﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Errors;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Rights;
using EffiProz.Core.Store;

namespace EffiProz.Core
{
    public class ParserDQL : ParserBase
    {
        protected Database database;
        protected Session session;
        public CompileContext compileContext;
        public CoreException lastError;

        //

        /**
         *  Constructs a new Parser object with the given context.
         *
         * @param  session the connected context
         * @param  t the token source from which to parse commands
         */
        public ParserDQL(Session session, Scanner t)
            : base(t)
        {

            this.session = session;
            database = session.getDatabase();
            compileContext = new CompileContext(session);
        }

        /**
         *  Resets this parse context with the given SQL character sequence.
         *
         * @param sql a new SQL character sequence to replace the current one
         */
        public override void reset(String sql)
        {

            base.reset(sql);
            compileContext.reset();

            lastError = null;
        }

        public void checkIsSchemaObjectName()
        {

            if (database.sqlEnforceNames)
            {
                checkIsNonReservedIdentifier();
            }
            else
            {
                checkIsNonCoreReservedIdentifier();
            }
        }

        public SqlType readTypeDefinition(bool includeUserTypes)
        {

            int typeNumber = int.MinValue;
            bool hasLength = false;
            bool hasScale = false;

            checkIsIdentifier();

            if (token.namePrefix == null)
            {
                typeNumber = SqlType.getTypeNr(token.tokenString);
            }

            if (typeNumber == int.MinValue)
            {
                if (includeUserTypes)
                {
                    checkIsSchemaObjectName();

                    String schemaName = session.getSchemaName(token.namePrefix);
                    SqlType type = database.schemaManager.getDomain(token.tokenString,
                        schemaName, false);

                    if (type != null)
                    {
                        read();

                        return type;
                    }
                }

                throw Error.error(ErrorCode.X_42509, token.tokenString);
            }

            read();

            switch (typeNumber)
            {

                case Types.SQL_CHAR:
                    if (token.tokenType == Tokens.VARYING)
                    {
                        read();

                        typeNumber = Types.SQL_VARCHAR;
                    }
                    else if (token.tokenType == Tokens.LARGE)
                    {
                        readThis(Tokens.OBJECT);
                        read();

                        typeNumber = Types.SQL_CLOB;
                    }
                    break;

                case Types.SQL_DOUBLE:
                    if (token.tokenType == Tokens.PRECISION)
                    {
                        read();
                    }
                    break;

                case Types.SQL_BINARY:
                    if (token.tokenType == Tokens.VARYING)
                    {
                        read();

                        typeNumber = Types.SQL_VARBINARY;
                    }
                    else if (token.tokenType == Tokens.LARGE)
                    {
                        readThis(Tokens.OBJECT);
                        read();

                        typeNumber = Types.SQL_BLOB;
                    }
                    break;

                case Types.SQL_BIT:
                    if (token.tokenType == Tokens.VARYING)
                    {
                        read();

                        typeNumber = Types.SQL_BIT_VARYING;
                    }
                    break;

                case Types.SQL_INTERVAL:
                    return readIntervalType(false);

       
            }

            long length = typeNumber == Types.SQL_TIMESTAMP
                          ? DTIType.defaultTimestampFractionPrecision
                          : 0;
            int scale = 0;

            if (Types.requiresPrecision(typeNumber)
                    && token.tokenType != Tokens.OPENBRACKET
                    && database.sqlEnforceSize)
            {
                throw Error.error(ErrorCode.X_42599,
                                  SqlType.getDefaultType(typeNumber).getNameString());
            }

            if (Types.acceptsPrecision(typeNumber))
            {
                if (token.tokenType == Tokens.OPENBRACKET)
                {
                    int multiplier = 1;

                    read();

                    switch (token.tokenType)
                    {

                        case Tokens.X_VALUE:
                            if (token.dataType.typeCode != Types.SQL_INTEGER
                                    && token.dataType.typeCode
                                       != Types.SQL_BIGINT)
                            {
                                throw unexpectedToken();
                            }
                            break;

                        case Tokens.X_LOB_SIZE:
                            if (typeNumber == Types.SQL_BLOB
                                    || typeNumber == Types.SQL_CLOB)
                            {
                                switch (token.lobMultiplierType)
                                {

                                    case Tokens.K:
                                        multiplier = 1024;
                                        break;

                                    case Tokens.M:
                                        multiplier = 1024 * 1024;
                                        break;

                                    case Tokens.G:
                                        multiplier = 1024 * 1024 * 1024;
                                        break;

                                    case Tokens.P:
                                    case Tokens.T:
                                    default:
                                        throw unexpectedToken();
                                }

                                break;
                            }
                            else
                            {
                                throw unexpectedToken(token.getFullString());
                            }
                        default:
                            throw unexpectedToken();
                    }

                    hasLength = true;
                    length = Convert.ToInt64(token.tokenValue);

                    if (length < 0
                            || (length == 0
                                && !Types.acceptsZeroPrecision(typeNumber)))
                    {
                        throw Error.error(ErrorCode.X_42592);
                    }

                    length *= multiplier;

                    read();

                    if (typeNumber == Types.SQL_CHAR
                            || typeNumber == Types.SQL_VARCHAR
                            || typeNumber == Types.SQL_CLOB)
                    {
                        if (token.tokenType == Tokens.CHARACTERS)
                        {
                            read();
                        }
                        else if (token.tokenType == Tokens.OCTETS)
                        {
                            read();

                            length /= 2;
                        }
                    }

                    if (Types.acceptsScaleCreateParam(typeNumber)
                            && token.tokenType == Tokens.COMMA)
                    {
                        read();

                        scale = readInteger();

                        if (scale < 0)
                        {
                            throw Error.error(ErrorCode.X_42592);
                        }

                        hasScale = true;
                    }

                    readThis(Tokens.CLOSEBRACKET);
                }
                else if (typeNumber == Types.SQL_BIT)
                {
                    length = 1;
                }
                else if (typeNumber == Types.SQL_BLOB
                         || typeNumber == Types.SQL_CLOB)
                {
                    length = BlobType.defaultBlobSize;
                }
                else if (database.sqlEnforceSize)
                {

                    // BIT is always BIT(1), regardless of sqlEnforceStringSize
                    if (typeNumber == Types.SQL_CHAR
                            || typeNumber == Types.SQL_BINARY)
                    {
                        length = 1;
                    }
                }

                if (typeNumber == Types.SQL_TIMESTAMP
                        || typeNumber == Types.SQL_TIME)
                {
                    if (length > DTIType.maxFractionPrecision)
                    {
                        throw Error.error(ErrorCode.X_42592);
                    }

                    scale = (int)length;
                    length = 0;

                    if (token.tokenType == Tokens.WITH)
                    {
                        read();
                        readThis(Tokens.TIME);
                        readThis(Tokens.ZONE);

                        if (typeNumber == Types.SQL_TIMESTAMP)
                        {
                            typeNumber = Types.SQL_TIMESTAMP_WITH_TIME_ZONE;
                        }
                        else
                        {
                            typeNumber = Types.SQL_TIME_WITH_TIME_ZONE;
                        }
                    }
                    else if (token.tokenType == Tokens.WITHOUT)
                    {
                        read();
                        readThis(Tokens.TIME);
                        readThis(Tokens.ZONE);
                    }
                }
            }

            switch (typeNumber)
            {

                case Types.LONGVARCHAR:
                    {
                        typeNumber = Types.SQL_VARCHAR;

                        if (!hasLength)
                        {
                            length = 1024 * 1024;
                        }

                        break;
                    }
                case Types.LONGVARBINARY:
                    {
                        typeNumber = Types.SQL_VARBINARY;

                        if (!hasLength)
                        {
                            length = 1024 * 1024;
                        }

                        break;
                    }
                case Types.SQL_CHAR:
                case Types.SQL_VARCHAR:
                    if (!hasLength && !database.sqlEnforceSize)
                    {
                        length = 32 * 1024;
                    }
                    break;

                case Types.SQL_DECIMAL:
                case Types.SQL_NUMERIC:
                    if (!hasLength && !hasScale && !database.sqlEnforceSize)
                    {
                        length = NumberType.defaultNumericPrecision;
                        scale = NumberType.defaultNumericScale;
                    }
                    break;
            }

            SqlType typeObject = SqlType.getType(typeNumber, 0, length, scale);

            if (typeObject.isCharacterType())
            {
                if (token.tokenType == Tokens.CHARACTER)
                {
                    read();
                    readThis(Tokens.SET);
                    checkIsSchemaObjectName();

                    String schemaName = session.getSchemaName(token.namePrefix);
                    Charset charset =
                        (Charset)database.schemaManager.getSchemaObject(
                            token.tokenString, schemaName, SchemaObjectTypes.CHARSET);

                    read();
                }
            }

            return typeObject;
        }

        public void readSimpleColumnNames(OrderedHashSet columns,
                                   RangeVariable rangeVar)
        {

            while (true)
            {
                ColumnSchema col = readSimpleColumnName(rangeVar);

                if (!columns.add(col.getName().name))
                {
                    throw Error.error(ErrorCode.X_42579, col.getName().name);
                }

                if (readIfThis(Tokens.COMMA))
                {
                    continue;
                }

                if (token.tokenType == Tokens.CLOSEBRACKET)
                {
                    break;
                }

                throw unexpectedToken();
            }
        }

        public void readColumnNames(OrderedHashSet columns, RangeVariable[] rangeVars)
        {

            while (true)
            {
                ColumnSchema col = readColumnName(rangeVars);

                if (!columns.add(col.getName().name))
                {
                    throw Error.error(ErrorCode.X_42579, col.getName().name);
                }

                if (readIfThis(Tokens.COMMA))
                {
                    continue;
                }

                if (token.tokenType == Tokens.CLOSEBRACKET)
                {
                    break;
                }

                throw unexpectedToken();
            }
        }

        public void readColumnNamesForSelectInto(OrderedHashSet columns,
                                          RangeVariable[] rangeVars)
        {

            while (true)
            {
                ColumnSchema col = readColumnName(rangeVars);

                if (!columns.add(col.getName().name))
                {
                    throw Error.error(ErrorCode.X_42579, col.getName().name);
                }

                if (readIfThis(Tokens.COMMA))
                {
                    continue;
                }

                if (token.tokenType == Tokens.FROM)
                {
                    break;
                }

                throw unexpectedToken();
            }
        }

        public void readSimpleColumnNames(OrderedHashSet columns, Table table)
        {

            while (true)
            {
                ColumnSchema col = readSimpleColumnName(table);

                if (!columns.add(col.getName().name))
                {
                    throw Error.error(ErrorCode.X_42577, col.getName().name);
                }

                if (readIfThis(Tokens.COMMA))
                {
                    continue;
                }

                if (token.tokenType == Tokens.CLOSEBRACKET)
                {
                    break;
                }

                throw unexpectedToken();
            }
        }

        public QNameManager.QName[] readColumnNames(QNameManager.QName tableName)
        {

            BitMap quotedFlags = new BitMap(32);
            OrderedHashSet set = readColumnNames(quotedFlags, false);
            QNameManager.QName[] colList = new QNameManager.QName[set.size()];

            for (int i = 0; i < colList.Length; i++)
            {
                String name = (String)set.get(i);
                bool quoted = quotedFlags.isSet(i);

                colList[i] = database.nameManager.newHsqlName(tableName.schema,
                        name, quoted, SchemaObjectTypes.COLUMN, tableName);
            }

            return colList;
        }

        public OrderedHashSet readColumnNames(bool readAscDesc)
        {
            return readColumnNames(null, readAscDesc);
        }

        public OrderedHashSet readColumnNames(BitMap quotedFlags, bool readAscDesc)
        {

            readThis(Tokens.OPENBRACKET);

            OrderedHashSet set = readColumnNameList(quotedFlags, readAscDesc);

            readThis(Tokens.CLOSEBRACKET);

            return set;
        }

        public OrderedHashSet readColumnNameList(BitMap quotedFlags,
                                          bool readAscDesc)
        {

            int i = 0;
            OrderedHashSet set = new OrderedHashSet();

            while (true)
            {
                if (session.isProcessingScript())
                {

                    // for old scripts
                    if (!isSimpleName())
                    {
                        token.isDelimitedIdentifier = true;
                    }
                }
                else
                {
                    checkIsSimpleName();
                }

                if (!set.add(token.tokenString))
                {
                    throw Error.error(ErrorCode.X_42577, token.tokenString);
                }

                if (quotedFlags != null && isDelimitedIdentifier())
                {
                    quotedFlags.set(i);
                }

                read();

                i++;

                if (readAscDesc)
                {
                    if (token.tokenType == Tokens.ASC
                            || token.tokenType == Tokens.DESC)
                    {
                        read();
                    }
                }

                if (readIfThis(Tokens.COMMA))
                {
                    continue;
                }

                break;
            }

            return set;
        }

        public SubQuery getViewSubquery(View v)
        {

            SubQuery sq = v.viewSubQuery;

            for (int i = 0; i < v.viewSubqueries.Length; i++)
            {
                compileContext.subQueryList.add(v.viewSubqueries[i]);
            }

            return sq;
        }

        public int XreadUnionType()
        {

            int unionType = QueryExpression.NOUNION;

            switch (token.tokenType)
            {

                case Tokens.UNION:
                    read();

                    unionType = QueryExpression.UNION;

                    if (token.tokenType == Tokens.ALL)
                    {
                        unionType = QueryExpression.UNION_ALL;

                        read();
                    }
                    else if (token.tokenType == Tokens.DISTINCT)
                    {
                        read();
                    }
                    break;

                case Tokens.INTERSECT:
                    read();

                    unionType = QueryExpression.INTERSECT;

                    if (token.tokenType == Tokens.ALL)
                    {
                        unionType = QueryExpression.INTERSECT_ALL;

                        read();
                    }
                    else if (token.tokenType == Tokens.DISTINCT)
                    {
                        read();
                    }
                    break;

                case Tokens.EXCEPT:
                case Tokens.MINUS_EXCEPT:
                    read();

                    unionType = QueryExpression.EXCEPT;

                    if (token.tokenType == Tokens.ALL)
                    {
                        unionType = QueryExpression.EXCEPT_ALL;

                        read();
                    }
                    else if (token.tokenType == Tokens.DISTINCT)
                    {
                        read();
                    }
                    break;

                default:
                    break;
            }

            return unionType;
        }

        public void XreadUnionCorrespondingClause(QueryExpression queryExpression)
        {

            if (token.tokenType == Tokens.CORRESPONDING)
            {
                read();
                queryExpression.setUnionCorresoponding();

                if (token.tokenType == Tokens.BY)
                {
                    read();

                    OrderedHashSet names = readColumnNames(false);

                    queryExpression.setUnionCorrespondingColumns(names);
                }
            }
        }

        public QueryExpression XreadQueryExpression()
        {

            if (token.tokenType == Tokens.WITH)
            {
                throw base.unsupportedFeature();
            }

            QueryExpression queryExpression = XreadQueryExpressionBody();
            SortAndSlice sortAndSlice = XreadOrderByExpression();

            if (queryExpression.sortAndSlice == null)
            {
                queryExpression.addSortAndSlice(sortAndSlice);
            }
            else
            {
                if (queryExpression.sortAndSlice.hasLimit())
                {
                    if (sortAndSlice.hasLimit())
                    {
                        throw Error.error(ErrorCode.X_42549);
                    }

                    for (int i = 0; i < sortAndSlice.exprList.size(); i++)
                    {
                        Expression e = (Expression)sortAndSlice.exprList.get(i);

                        queryExpression.sortAndSlice.addOrderExpression(e);
                    }
                }
                else
                {
                    queryExpression.addSortAndSlice(sortAndSlice);
                }
            }

            return queryExpression;
        }

        QueryExpression XreadQueryExpressionBody()
        {

            QueryExpression queryExpression = XreadQueryTerm();

            while (true)
            {
                switch (token.tokenType)
                {

                    case Tokens.UNION:
                    case Tokens.EXCEPT:
                    case Tokens.MINUS_EXCEPT:
                        {
                            queryExpression = XreadSetOperation(queryExpression);

                            break;
                        }
                    default:
                        {
                            return queryExpression;
                        }
                }
            }
        }

        QueryExpression XreadQueryTerm()
        {

            QueryExpression queryExpression = XreadQueryPrimary();

            while (true)
            {
                if (token.tokenType == Tokens.INTERSECT)
                {
                    queryExpression = XreadSetOperation(queryExpression);
                }
                else
                {
                    return queryExpression;
                }
            }
        }

        private QueryExpression XreadSetOperation(
                QueryExpression queryExpression)
        {

            queryExpression = new QueryExpression(compileContext, queryExpression);

            int unionType = XreadUnionType();

            XreadUnionCorrespondingClause(queryExpression);

            QueryExpression rightQueryExpression = XreadQueryTerm();

            queryExpression.addUnion(rightQueryExpression, unionType);

            return queryExpression;
        }

        QueryExpression XreadQueryPrimary()
        {

            switch (token.tokenType)
            {

                case Tokens.TABLE:
                case Tokens.VALUES:
                case Tokens.SELECT:
                    {
                        QuerySpecification select = XreadSimpleTable();

                        return select;
                    }
                case Tokens.OPENBRACKET:
                    {
                        read();

                        QueryExpression queryExpression = XreadQueryExpressionBody();
                        SortAndSlice sortAndSlice = XreadOrderByExpression();

                        readThis(Tokens.CLOSEBRACKET);

                        if (queryExpression.sortAndSlice == null)
                        {
                            queryExpression.addSortAndSlice(sortAndSlice);
                        }
                        else
                        {
                            if (queryExpression.sortAndSlice.hasLimit())
                            {
                                if (sortAndSlice.hasLimit())
                                {
                                    throw Error.error(ErrorCode.X_42549);
                                }

                                for (int i = 0; i < sortAndSlice.exprList.size();
                                        i++)
                                {
                                    Expression e =
                                        (Expression)sortAndSlice.exprList.get(i);

                                    queryExpression.sortAndSlice.addOrderExpression(e);
                                }
                            }
                            else
                            {
                                queryExpression.addSortAndSlice(sortAndSlice);
                            }
                        }

                        return queryExpression;
                    }
                default:
                    {
                        throw unexpectedToken();
                    }
            }
        }

        QuerySpecification XreadSimpleTable()
        {

            QuerySpecification select;

            switch (token.tokenType)
            {

                case Tokens.TABLE:
                    {
                        read();

                        Table table = readTableName();

                        select = new QuerySpecification(session, table,
                                                        compileContext);

                        break;
                    }
                case Tokens.VALUES:
                    {
                        read();

                        SubQuery sq = XreadRowValueExpressionList();

                        select = new QuerySpecification(session, sq.getTable(),
                                                        compileContext);

                        break;
                    }
                case Tokens.SELECT:
                    {
                        select = XreadQuerySpecification();

                        break;
                    }
                default:
                    {
                        throw unexpectedToken();
                    }
            }

            return select;
        }

        public QuerySpecification XreadQuerySpecification()
        {

            QuerySpecification select = XreadSelect();

            XreadTableExpression(select);

            return select;
        }

        public void XreadTableExpression(QuerySpecification select)
        {
            XreadFromClause(select);
            readWhereGroupHaving(select);
        }

        public QuerySpecification XreadSelect()
        {

            QuerySpecification select = new QuerySpecification(compileContext);

            readThis(Tokens.SELECT);

            if (token.tokenType == Tokens.TOP || token.tokenType == Tokens.LIMIT)
            {
                SortAndSlice sortAndSlice = XreadTopOrLimit();

                if (sortAndSlice != null)
                {
                    select.addSortAndSlice(sortAndSlice);
                }
            }

            if (token.tokenType == Tokens.DISTINCT)
            {
                select.isDistinctSelect = true;

                read();
            }
            else if (token.tokenType == Tokens.ALL)
            {
                read();
            }

            while (true)
            {
                Expression e = XreadValueExpression();

                if (token.tokenType == Tokens.AS)
                {
                    read();
                    checkIsNonCoreReservedIdentifier();
                }

                if (isNonCoreReservedIdentifier())
                {
                    e.setAlias(QNameManager.getSimpleName(token.tokenString,
                            isDelimitedIdentifier()));
                    read();
                }

                select.addSelectColumnExpression(e);

                if (token.tokenType == Tokens.FROM)
                {
                    break;
                }

                if (token.tokenType == Tokens.INTO)
                {
                    break;
                }

                if (readIfThis(Tokens.COMMA))
                {
                    continue;
                }

                throw unexpectedToken();
            }

            return select;
        }

        void XreadFromClause(QuerySpecification select)
        {
       
            readThis(Tokens.FROM);

            while (true)
            {
                XreadTableReference(select);

                if (readIfThis(Tokens.COMMA))
                {
                    continue;
                }

                break;
            }
        }

        void XreadTableReference(QuerySpecification select)
        {

            bool natural = false;
            RangeVariable range = readTableOrSubquery();

            select.addRangeVariable(range);

            while (true)
            {
                int type = token.tokenType;
                bool left = false;
                bool right = false;
                bool end = false;

                type = token.tokenType;

                switch (token.tokenType)
                {

                    case Tokens.INNER:
                        read();
                        readThis(Tokens.JOIN);
                        break;

                    case Tokens.CROSS:
                        if (natural)
                        {
                            throw unexpectedToken();
                        }

                        read();
                        readThis(Tokens.JOIN);
                        break;

                    case Tokens.UNION:
                        if (natural)
                        {
                            throw unexpectedToken();
                        }

                        int position = getPosition();

                        read();

                        if (token.tokenType == Tokens.JOIN)
                        {
                            read();

                            break;
                        }
                        else
                        {
                            rewind(position);

                            end = true;

                            break;
                        }
                    case Tokens.NATURAL:
                        if (natural)
                        {
                            throw unexpectedToken();
                        }

                        read();

                        natural = true;

                        continue;
                    case Tokens.LEFT:
                        read();
                        readIfThis(Tokens.OUTER);
                        readThis(Tokens.JOIN);

                        left = true;
                        break;

                    case Tokens.RIGHT:
                        read();
                        readIfThis(Tokens.OUTER);
                        readThis(Tokens.JOIN);

                        right = true;
                        break;

                    case Tokens.FULL:
                        read();
                        readIfThis(Tokens.OUTER);
                        readThis(Tokens.JOIN);

                        left = true;
                        right = true;
                        break;

                    case Tokens.JOIN:
                        read();

                        type = Tokens.INNER;
                        break;

                    case Tokens.COMMA:
                        if (natural)
                        {
                            throw unexpectedToken();
                        }

                        read();

                        type = Tokens.CROSS;
                        break;

                    default:
                        if (natural)
                        {
                            throw unexpectedToken();
                        }

                        end = true;
                        break;
                }

                if (end)
                {
                    break;
                }

                range = readTableOrSubquery();

                Expression condition = null;

                switch (type)
                {

                    case Tokens.CROSS:
                        select.addRangeVariable(range);
                        break;

                    case Tokens.UNION:
                        select.addRangeVariable(range);

                        condition = Expression.EXPR_FALSE;

                        range.setJoinType(true, true);
                        break;

                    case Tokens.LEFT:
                    case Tokens.RIGHT:
                    case Tokens.INNER:
                    case Tokens.FULL:
                        {
                            if (natural)
                            {
                                OrderedHashSet columns =
                                    range.getUniqueColumnNameSet();

                                condition = select.getEquiJoinExpressions(columns,
                                        range, false);

                                select.addRangeVariable(range);
                            }
                            else if (token.tokenType == Tokens.USING)
                            {
                                read();

                                OrderedHashSet columns = new OrderedHashSet();

                                readThis(Tokens.OPENBRACKET);
                                readSimpleColumnNames(columns, range);
                                readThis(Tokens.CLOSEBRACKET);

                                condition = select.getEquiJoinExpressions(columns,
                                        range, true);

                                select.addRangeVariable(range);
                            }
                            else if (token.tokenType == Tokens.ON)
                            {
                                read();

                                condition = XreadBooleanValueExpression();

                                select.addRangeVariable(range);

                                // must ensure references are limited to the current table
                                //                        select.finaliseRangeVariables();
                                //                        select.resolveColumnReferencesAndAllocate(condition);
                            }
                            else
                            {
                                throw Error.error(ErrorCode.X_42581);
                            }

                            range.setJoinType(left, right);

                            break;
                        }
                }

                range.addJoinCondition(condition);

                natural = false;
            }
        }

        Expression getRowExpression(OrderedHashSet columnNames)
        {

            Expression[] elements = new Expression[columnNames.size()];

            for (int i = 0; i < elements.Length; i++)
            {
                String name = (String)columnNames.get(i);

                elements[i] = new ExpressionColumn(null, null, name);
            }

            return new Expression(OpTypes.ROW, elements);
        }

        void readWhereGroupHaving(QuerySpecification select)
        {

            // where
            if (token.tokenType == Tokens.WHERE)
            {
                read();

                Expression e = XreadBooleanValueExpression();

                select.addQueryCondition(e);
            }

            // group by
            if (token.tokenType == Tokens.GROUP)
            {
                read();
                readThis(Tokens.BY);

                while (true)
                {
                    Expression e = XreadValueExpression();

                    select.addGroupByColumnExpression(e);

                    if (token.tokenType == Tokens.COMMA)
                    {
                        read();

                        continue;
                    }

                    break;
                }
            }

            // having
            if (token.tokenType == Tokens.HAVING)
            {
                read();

                Expression e = XreadBooleanValueExpression();

                select.addHavingExpression(e);
            }
        }

        SortAndSlice XreadOrderByExpression()
        {

            SortAndSlice sortAndSlice = null;

            if (token.tokenType == Tokens.ORDER)
            {
                read();
                readThis(Tokens.BY);

                sortAndSlice = XreadOrderBy();
            }

            if (token.tokenType == Tokens.LIMIT || token.tokenType == Tokens.FETCH
                    || token.tokenType == Tokens.OFFSET)
            {
                if (sortAndSlice == null)
                {
                    sortAndSlice = new SortAndSlice();
                }

                XreadLimit(sortAndSlice);
            }

            return sortAndSlice == null ? SortAndSlice.noSort
                                        : sortAndSlice;
        }

        private SortAndSlice XreadTopOrLimit()
        {

            Expression e1 = null;
            Expression e2 = null;

            if (token.tokenType == Tokens.LIMIT)
            {
                int position = getPosition();

                read();

                e1 = XreadSimpleValueSpecificationOrNull();

                if (e1 == null)
                {
                    base.rewind(position);

                    return null;
                }

                e2 = XreadSimpleValueSpecificationOrNull();

                if (e2 == null)
                {
                    throw Error.error(ErrorCode.X_42565,
                                      ErrorCode.M_INVALID_LIMIT);
                }
            }
            else if (token.tokenType == Tokens.TOP)
            {
                int position = getPosition();

                read();

                e2 = XreadSimpleValueSpecificationOrNull();

                if (e2 == null)
                {
                    base.rewind(position);

                    return null;
                }

                e1 = new ExpressionValue(0, SqlType.SQL_INTEGER);
            }

            bool valid = true;

            if (e1.isParam())
            {
                e1.setDataType(session, SqlType.SQL_INTEGER);
            }
            else
            {
                valid = (e1.getDataType().typeCode == Types.SQL_INTEGER
                         && Convert.ToUInt32(e1.getValue(null)) >= 0);
            }

            if (e2.isParam())
            {
                e2.setDataType(session, SqlType.SQL_INTEGER);
            }
            else
            {
                valid &= (e2.getDataType().typeCode == Types.SQL_INTEGER
                          && Convert.ToUInt32(e2.getValue(null)) >= 0);
            }

            if (valid)
            {
                SortAndSlice sortAndSlice = new SortAndSlice();

                sortAndSlice.addLimitCondition(new ExpressionOp(OpTypes.LIMIT, e1,
                        e2));

                return sortAndSlice;
            }

            throw Error.error(ErrorCode.X_42565, ErrorCode.M_INVALID_LIMIT);
        }

        private void XreadLimit(SortAndSlice sortAndSlice)
        {

            Expression e1 = null;
            Expression e2 = null;

            if (token.tokenType == Tokens.OFFSET)
            {
                read();

                e1 = XreadSimpleValueSpecificationOrNull();

                if (e1 == null)
                {
                    throw Error.error(ErrorCode.X_42565,
                                      ErrorCode.M_INVALID_LIMIT);
                }
            }

            if (token.tokenType == Tokens.LIMIT)
            {
                read();

                e2 = XreadSimpleValueSpecificationOrNull();

                if (e2 == null)
                {
                    throw Error.error(ErrorCode.X_42565,
                                      ErrorCode.M_INVALID_LIMIT);
                }

                if (e1 == null && token.tokenType == Tokens.OFFSET)
                {
                    read();

                    e1 = XreadSimpleValueSpecificationOrNull();
                }
            }
            else if (token.tokenType == Tokens.FETCH)
            {
                read();

                if (token.tokenType == Tokens.FIRST
                        || token.tokenType == Tokens.NEXT)
                {
                    read();
                }

                e2 = XreadSimpleValueSpecificationOrNull();

                if (e2 == null)
                {
                    e2 = new ExpressionValue(1,
                                             SqlType.SQL_INTEGER);
                }

                if (token.tokenType == Tokens.ROW
                        || token.tokenType == Tokens.ROWS)
                {
                    read();
                }

                readThis(Tokens.ONLY);
            }

            if (e1 == null)
            {
                e1 = new ExpressionValue(0, SqlType.SQL_INTEGER);
            }

            bool valid = true;

            if (e1.isParam())
            {
                e1.setDataType(session, SqlType.SQL_INTEGER);
            }
            else
            {
                valid = (e1.getDataType().typeCode == Types.SQL_INTEGER
                         && Convert.ToInt32(e1.getValue(null)) >= 0);
            }

            if (e2.isParam())
            {
                e2.setDataType(session, SqlType.SQL_INTEGER);
            }
            else
            {
                valid &= (e2.getDataType().typeCode == Types.SQL_INTEGER
                          && Convert.ToInt32(e2.getValue(null)) >= 0);
            }

            if (valid)
            {
                sortAndSlice.addLimitCondition(new ExpressionOp(OpTypes.LIMIT, e1,
                        e2));

                return;
            }

            throw Error.error(ErrorCode.X_42565, ErrorCode.M_INVALID_LIMIT);
        }

        private SortAndSlice XreadOrderBy()
        {

            SortAndSlice sortAndSlice = new SortAndSlice();

            while (true)
            {
                Expression e = XreadValueExpression();
                ExpressionOrderBy o = new ExpressionOrderBy(e);

                if (token.tokenType == Tokens.DESC)
                {
                    o.setDescending();
                    read();
                }
                else if (token.tokenType == Tokens.ASC)
                {
                    read();
                }

                if (token.tokenType == Tokens.NULLS)
                {
                    read();

                    if (token.tokenType == Tokens.FIRST)
                    {
                        read();
                    }
                    else if (token.tokenType == Tokens.LAST)
                    {
                        read();
                        o.setNullsLast();
                    }
                    else
                    {
                        throw unexpectedToken();
                    }
                }

                sortAndSlice.addOrderExpression(o);

                if (token.tokenType == Tokens.COMMA)
                {
                    read();

                    continue;
                }

                break;
            }

            return sortAndSlice;
        }

        protected RangeVariable readSimpleRangeVariable(int operation)
        {

            Table table = readTableName();
            QNameManager.SimpleName alias = null;

            if (operation != StatementTypes.DELETE_WHERE)
            {
                if (token.tokenType == Tokens.AS)
                {
                    read();
                    checkIsNonCoreReservedIdentifier();
                }

                if (isNonCoreReservedIdentifier())
                {
                    alias = QNameManager.getSimpleName(token.tokenString,
                                                          isDelimitedIdentifier());

                    read();
                }
            }

            if (table.isView())
            {
                switch (operation)
                {

                    case StatementTypes.MERGE:
                        if (!table.isUpdatable() || !table.isInsertable())
                        {
                            throw Error.error(ErrorCode.X_42545);
                        }
                        break;

                    case StatementTypes.UPDATE_WHERE:
                    case StatementTypes.DELETE_WHERE:
                        if (!table.isUpdatable())
                        {
                            throw Error.error(ErrorCode.X_42545);
                        }
                        break;
                }

                SubQuery sq = getViewSubquery((View)table);

                table = sq.getTable();
            }

            RangeVariable range = new RangeVariable(table, alias, null, null,
                compileContext);

            return range;
        }

        /**
         * Creates a RangeVariable from the parse context. <p>
         */
        protected RangeVariable readTableOrSubquery()
        {

            Table table = null;
            QNameManager.SimpleName alias = null;
            OrderedHashSet columnList = null;
            BitMap columnNameQuoted = null;
            QNameManager.SimpleName[] columnNameList = null;

            if (token.tokenType == Tokens.OPENBRACKET)
            {
                Expression e = XreadTableSubqueryOrJoinedTable();

                table = e.subQuery.getTable();
            }
            else
            {
                table = readTableName();

                if (table.isView())
                {
                    SubQuery sq = getViewSubquery((View)table);

                    //                sq.queryExpression = ((View) table).queryExpression;
                    table = sq.getTable();
                }
            }

            bool hasAs = false;

            if (token.tokenType == Tokens.AS)
            {
                read();
                checkIsNonCoreReservedIdentifier();

                hasAs = true;
            }

            if (isNonCoreReservedIdentifier())
            {
                bool limit = token.tokenType == Tokens.LIMIT
                                || token.tokenType == Tokens.OFFSET;
                int position = getPosition();

                alias = QNameManager.getSimpleName(token.tokenString,
                                                      isDelimitedIdentifier());

                read();

                if (token.tokenType == Tokens.OPENBRACKET)
                {
                    columnNameQuoted = new BitMap(32);
                    columnList = readColumnNames(columnNameQuoted, false);
                }
                else if (!hasAs && limit)
                {
                    if (token.tokenType == Tokens.QUESTION
                            || token.tokenType == Tokens.X_VALUE)
                    {
                        alias = null;

                        rewind(position);
                    }
                }
            }

            if (columnList != null)
            {
                if (table.getColumnCount() != columnList.size())
                {
                    throw Error.error(ErrorCode.X_42593);
                }

                columnNameList = new QNameManager.SimpleName[columnList.size()];

                for (int i = 0; i < columnList.size(); i++)
                {
                    QNameManager.SimpleName name =
                        QNameManager.getSimpleName((String)columnList.get(i),
                                                      columnNameQuoted.isSet(i));

                    columnNameList[i] = name;
                }
            }

            RangeVariable range = new RangeVariable(table, alias, columnList,
                columnNameList, compileContext);

            return range;
        }

        private Expression readAggregate()
        {

            int tokenT = token.tokenType;
            Expression e;

            read();
            readThis(Tokens.OPENBRACKET);

            e = readAggregateExpression(tokenT);

            readThis(Tokens.CLOSEBRACKET);

            return e;
        }

        private Expression readAggregateExpression(int tokenT)
        {

            int type = ParserDQL.getExpressionType(tokenT);
            bool distinct = false;
            bool all = false;

            if (token.tokenType == Tokens.DISTINCT)
            {
                distinct = true;

                read();
            }
            else if (token.tokenType == Tokens.ALL)
            {
                all = true;

                read();
            }

            Expression e = XreadValueExpression();

            switch (type)
            {

                case OpTypes.COUNT:
                    if (e.getType() == OpTypes.MULTICOLUMN)
                    {
                        if (((ExpressionColumn)e).tableName != null)
                        {
                            throw unexpectedToken();
                        }

                        if (all || distinct)
                        {
                            throw unexpectedToken();
                        }

                        e.opType = OpTypes.ASTERISK;

                        break;
                    }
                    else
                    {
                        break;
                    }
                case OpTypes.STDDEV_POP:
                case OpTypes.STDDEV_SAMP:
                case OpTypes.VAR_POP:
                case OpTypes.VAR_SAMP:
                    if (all || distinct)
                    {
                        throw Error.error(ErrorCode.X_42582, all ? Tokens.T_ALL
                                                                 : Tokens
                                                                 .T_DISTINCT);
                    }
                    break;

                default:
                    if (e.getType() == OpTypes.ASTERISK)
                    {
                        throw unexpectedToken();
                    }
                    break;
            }

            Expression aggregateExp = new ExpressionAggregate(type, distinct, e);

            return aggregateExp;
        }

        //--------------------------------------
        // returns null
        // := <unsigned literal> | <general value specification>
        public Expression XreadValueSpecificationOrNull()
        {

            Expression e = null;
            bool minus = false;

            switch (token.tokenType)
            {

                case Tokens.PLUS:
                    read();
                    break;

                case Tokens.MINUS:
                    read();

                    minus = true;
                    break;
            }

            e = XreadUnsignedValueSpecificationOrNull();

            if (e == null)
            {
                return null;
            }

            if (minus)
            {
                e = new ExpressionArithmetic(OpTypes.NEGATE, e);
            }

            return e;
        }

        // returns null
        // <unsigned literl> | <general value specification>
        Expression XreadUnsignedValueSpecificationOrNull()
        {

            Expression e;

            switch (token.tokenType)
            {

                case Tokens.TRUE:
                    read();

                    return Expression.EXPR_TRUE;

                case Tokens.FALSE:
                    read();

                    return Expression.EXPR_FALSE;

                case Tokens.DEFAULT:
                    if (compileContext.contextuallyTypedExpression)
                    {
                        read();

                        e = new ExpressionColumn(OpTypes.DEFAULT);

                        return e;
                    }
                    break;

                case Tokens.NULL:
                    e = new ExpressionValue(null, null);

                    read();

                    return e;

                case Tokens.X_VALUE:
                    e = new ExpressionValue(token.tokenValue, token.dataType);

                    read();

                    return e;

                case Tokens.X_DELIMITED_IDENTIFIER:
                case Tokens.X_IDENTIFIER:
                    if (!token.isHostParameter)
                    {
                        return null;
                    }
                    e = new ExpressionColumn(OpTypes.NAMED_PARAM,token.tokenString);

                    compileContext.parameters.add(e);
                    read();

                    return e;
                case Tokens.QUESTION:
                    e = new ExpressionColumn(OpTypes.DYNAMIC_PARAM);

                    compileContext.parameters.add(e);
                    read();

                    return e;

                case Tokens.COLLATION:
                    return XreadCurrentCollationSpec();

                case Tokens.VALUE:
                case Tokens.CURRENT_CATALOG:
                case Tokens.CURRENT_DEFAULT_TRANSFORM_GROUP:
                case Tokens.CURRENT_PATH:
                case Tokens.CURRENT_ROLE:
                case Tokens.CURRENT_SCHEMA:
                case Tokens.CURRENT_TRANSFORM_GROUP_FOR_TYPE:
                case Tokens.CURRENT_USER:
                case Tokens.SESSION_USER:
                case Tokens.SYSTEM_USER:
                case Tokens.USER:
                    FunctionSQL function =
                        FunctionSQL.newSQLFunction(token.tokenString,
                                                   compileContext);

                    if (function == null)
                    {
                        return null;
                    }

                    return readSQLFunction(function);

                // read SQL parameter reference
            }

            return null;
        }

        // <unsigned literl> | <parameter>
        Expression XreadSimpleValueSpecificationOrNull()
        {

            Expression e;

            switch (token.tokenType)
            {

                case Tokens.X_VALUE:
                    e = new ExpressionValue(token.tokenValue, token.dataType);

                    read();

                    return e;
                case Tokens.X_DELIMITED_IDENTIFIER:
                case Tokens.X_IDENTIFIER:
                    if (!token.isHostParameter)
                    {
                        return null;
                    }
                    e = new ExpressionColumn(OpTypes.NAMED_PARAM, token.tokenString);

                    compileContext.parameters.add(e);
                    read();

                    return e;
                case Tokens.QUESTION:
                    e = new ExpressionColumn(OpTypes.DYNAMIC_PARAM);

                    compileContext.parameters.add(e);
                    read();

                    return e;

                default:
                    return null;
            }
        }

        // combined <value expression primary> and <predicate>
        // exclusively called
        // <explicit row value constructor> needed for predicate
        Expression XreadAllTypesValueExpressionPrimary(bool boole)
        {

            Expression e = null;

            switch (token.tokenType)
            {

                case Tokens.EXISTS:
                case Tokens.UNIQUE:
                    if (boole)
                    {
                        return XreadPredicate();
                    }
                    break;

                case Tokens.ROW:
                    if (boole)
                    {
                        break;
                    }

                    read();
                    readThis(Tokens.OPENBRACKET);

                    e = XreadRowElementList(true);

                    readThis(Tokens.CLOSEBRACKET);
                    break;

                default:
                    e = XreadSimpleValueExpressionPrimary();
                    break;
            }

            if (e == null && token.tokenType == Tokens.OPENBRACKET)
            {
                read();

                e = XreadRowElementList(true);

                readThis(Tokens.CLOSEBRACKET);
            }

            if (boole && e != null)
            {
                e = XreadPredicateRightPart(e);
            }

            return e;
        }

        // doesn't return null
        // <value expression primary> ::= <parenthesized value expression>
        // | <nonparenthesized value expression primary>
        Expression XreadValueExpressionPrimary()
        {

            Expression e;

            e = XreadSimpleValueExpressionPrimary();

            if (e != null)
            {
                return e;
            }

            if (token.tokenType == Tokens.OPENBRACKET)
            {
                read();

                e = XreadValueExpression();

                readThis(Tokens.CLOSEBRACKET);
            }
            else
            {
                return null;
            }

            return e;
        }

        // returns null
        //  <row value special case> :== this
        // <bool predicand> :== this | <parenthesized bool value expression>
        Expression XreadSimpleValueExpressionPrimary()
        {

            Expression e;

            e = XreadUnsignedValueSpecificationOrNull();

            if (e != null)
            {
                return e;
            }

            switch (token.tokenType)
            {

                case Tokens.OPENBRACKET:
                    int position = getPosition();

                    read();

                    int subqueryPosition = getPosition();

                    readOpenBrackets();

                    switch (token.tokenType)
                    {

                        case Tokens.TABLE:
                        case Tokens.VALUES:
                        case Tokens.SELECT:
                            SubQuery sq = null;

                            rewind(subqueryPosition);

                            try
                            {
                                sq = XreadSubqueryBody(false,
                                                       OpTypes.SCALAR_SUBQUERY);

                                readThis(Tokens.CLOSEBRACKET);
                            }
                            catch (CoreException ex)
                            {
                                compileContext.resetSubQueryLevel();
                                ex.setLevel(compileContext.subQueryDepth);

                                if (lastError == null
                                        || lastError.getLevel() < ex.getLevel())
                                {
                                    lastError = ex;
                                }

                                rewind(position);

                                return null;
                            }

                            if (!sq.queryExpression.isSingleColumn())
                            {
                                throw Error.error(ErrorCode.W_01000);
                            }

                            return new Expression(OpTypes.SCALAR_SUBQUERY, sq);

                        default:
                            rewind(position);

                            return null;
                    }
                case Tokens.ASTERISK:
                    e = new ExpressionColumn(token.namePrePrefix,
                                             token.namePrefix);

                    recordExpressionForToken((ExpressionColumn)e);
                    read();

                    return e;

                case Tokens.CASEWHEN:
                    return readCaseWhenExpression();

                case Tokens.CASE:
                    return readCaseExpression();

                case Tokens.NULLIF:
                    return readNullIfExpression();

                case Tokens.COALESCE:
                case Tokens.IFNULL:
                    return readCoalesceExpression();

                case Tokens.CAST:
                case Tokens.CONVERT:
                    return readCastExpression();

                case Tokens.DATE:
                case Tokens.TIME:
                case Tokens.TIMESTAMP:
                case Tokens.INTERVAL:
                    e = readDateTimeIntervalLiteral();

                    if (e != null)
                    {
                        return e;
                    }
                    break;

                case Tokens.ANY:
                case Tokens.SOME:
                case Tokens.EVERY:
                case Tokens.COUNT:
                case Tokens.MAX:
                case Tokens.MIN:
                case Tokens.SUM:
                case Tokens.AVG:
                case Tokens.STDDEV_POP:
                case Tokens.STDDEV_SAMP:
                case Tokens.VAR_POP:
                case Tokens.VAR_SAMP:
                    return readAggregate();

                case Tokens.NEXT:
                    return readSequenceExpression();

                case Tokens.LEFT:
                case Tokens.RIGHT:

                    // CLI function names
                    break;

                default:
                    if (isCoreReservedKey())
                    {
                        throw unexpectedToken();
                    }
                    break;
            }

            return readColumnOrFunctionExpression();
        }

        // OK - composite production -
        // <numeric primary> <charactr primary> <binary primary> <datetime primary> <interval primary>
        Expression XreadAllTypesPrimary(bool boole)
        {

            Expression e = null;

            switch (token.tokenType)
            {

                case Tokens.SUBSTRING:
                case Tokens.SUBSTRING_REGEX:
                case Tokens.LOWER:
                case Tokens.UPPER:
                case Tokens.TRANSLATE_REGEX:
                case Tokens.TRIM:
                case Tokens.OVERLAY:
                case Tokens.NORMALIZE:

                //
                case Tokens.POSITION:
                case Tokens.OCCURRENCES_REGEX:
                case Tokens.POSITION_REGEX:
                case Tokens.EXTRACT:
                case Tokens.CHAR_LENGTH:
                case Tokens.CHARACTER_LENGTH:
                case Tokens.OCTET_LENGTH:
                case Tokens.CARDINALITY:
                case Tokens.ABS:
                case Tokens.MOD:
                case Tokens.LN:
                case Tokens.EXP:
                case Tokens.POWER:
                case Tokens.SQRT:
                case Tokens.FLOOR:
                case Tokens.CEILING:
                case Tokens.CEIL:
                case Tokens.WIDTH_BUCKET:
                    FunctionSQL function =
                        FunctionSQL.newSQLFunction(token.tokenString,
                                                   compileContext);

                    if (function == null)
                    {
                        throw unsupportedFeature();
                    }

                    e = readSQLFunction(function);

                    if (e != null)
                    {
                        break;
                    }
                    goto default;
                default:
                    e = XreadAllTypesValueExpressionPrimary(boole);
                    break;
            }

            e = XreadModifier(e);

            return e;
        }

        Expression XreadModifier(Expression e)
        {

            switch (token.tokenType)
            {

                case Tokens.AT:
                    {
                        read();

                        Expression e1 = null;

                        if (token.tokenType == Tokens.LOCAL)
                        {
                            read();
                        }
                        else
                        {
                            readThis(Tokens.TIME);
                            readThis(Tokens.ZONE);

                            e1 = XreadValueExpressionPrimary();

                            switch (token.tokenType)
                            {

                                case Tokens.YEAR:
                                case Tokens.MONTH:
                                case Tokens.DAY:
                                case Tokens.HOUR:
                                case Tokens.MINUTE:
                                case Tokens.SECOND:
                                    {
                                        IntervalType type = readIntervalType(false);

                                        if (e1.getType() == OpTypes.SUBTRACT)
                                        {
                                            e1.dataType = type;
                                        }
                                        else
                                        {
                                            e1 = new ExpressionOp(e1, type);
                                        }
                                    }
                                    break;
                            }
                        }

                        e = new ExpressionOp(OpTypes.ZONE_MODIFIER, e, e1);

                        break;
                    }
                case Tokens.YEAR:
                case Tokens.MONTH:
                case Tokens.DAY:
                case Tokens.HOUR:
                case Tokens.MINUTE:
                case Tokens.SECOND:
                    {
                        IntervalType type = readIntervalType(true);

                        if (e.getType() == OpTypes.SUBTRACT)
                        {
                            e.dataType = type;
                        }
                        else
                        {
                            e = new ExpressionOp(e, type);
                        }

                        break;
                    }
                case Tokens.COLLATE:
                    {
                        read();

                        SchemaObject collation =
                            database.schemaManager.getSchemaObject(token.namePrefix,
                                token.tokenString, SchemaObjectTypes.COLLATION);
                    }
                    break;
            }

            return e;
        }

        public Expression XreadValueExpressionWithContext()
        {

            Expression e;

            compileContext.contextuallyTypedExpression = true;
            e = XreadValueExpressionOrNull();
            compileContext.contextuallyTypedExpression = false;

            return e;
        }

        public Expression XreadValueExpressionOrNull()
        {
            return XreadAllTypesCommonValueExpression(true);
        }

        /**
         *     <value expression> ::=
         *   <common value expression>
         *   | <bool value expression>
         *   | <row value expression>
         *
         */
        public Expression XreadValueExpression()
        {
            return XreadAllTypesCommonValueExpression(true);
        }

        // union of <numeric | datetime | string | interval value expression>
        public Expression XreadRowOrCommonValueExpression()
        {
            return XreadAllTypesCommonValueExpression(false);
        }

        // union of <numeric | datetime | string | interval | bool value expression>
        // no <row value expression> and no <predicate>
        public Expression XreadAllTypesCommonValueExpression(bool boole)
        {

            Expression e = XreadAllTypesTerm(boole);
            int type = 0;
            bool end = false;

            while (true)
            {
                switch (token.tokenType)
                {

                    case Tokens.PLUS:
                        type = OpTypes.ADD;
                        boole = false;
                        break;

                    case Tokens.MINUS:
                        type = OpTypes.SUBTRACT;
                        boole = false;
                        break;

                    case Tokens.CONCAT:
                        type = OpTypes.CONCAT;
                        boole = false;
                        break;

                    case Tokens.OR:
                        if (boole)
                        {
                            type = OpTypes.OR;

                            break;
                        }

                        goto default;
                    // fall through
                    default:
                        end = true;
                        break;
                }

                if (end)
                {
                    break;
                }

                read();

                Expression a = e;

                e = XreadAllTypesTerm(boole);
                e = boole ? (Expression) new ExpressionLogical(type, a, e)
                          :(Expression) new ExpressionArithmetic(type, a, e);
            }

            return e;
        }

        Expression XreadAllTypesTerm(bool boole)
        {

            Expression e = XreadAllTypesFactor(boole);
            int type = 0;
            bool end = false;

            while (true)
            {
                switch (token.tokenType)
                {

                    case Tokens.ASTERISK:
                        type = OpTypes.MULTIPLY;
                        boole = false;
                        break;

                    case Tokens.DIVIDE:
                        type = OpTypes.DIVIDE;
                        boole = false;
                        break;

                    case Tokens.AND:
                        if (boole)
                        {
                            type = OpTypes.AND;

                            break;
                        }
                    goto default;
                    // fall through
                    default:
                        end = true;
                        break;
                }

                if (end)
                {
                    break;
                }

                read();

                Expression a = e;

                e = XreadAllTypesFactor(boole);

                if (e == null)
                {
                    throw unexpectedToken();
                }

                e = boole ? (Expression) new ExpressionLogical(type, a, e)
                          : (Expression) new ExpressionArithmetic(type, a, e);
            }

            return e;
        }

        Expression XreadAllTypesFactor(bool boole)
        {

            Expression e;
            bool minus = false;
            bool not = false;
            bool unknown = false;

            switch (token.tokenType)
            {

                case Tokens.PLUS:
                    read();

                    boole = false;
                    break;

                case Tokens.MINUS:
                    read();

                    boole = false;
                    minus = true;
                    break;

                case Tokens.NOT:
                    if (boole)
                    {
                        read();

                        not = true;
                    }
                    break;
            }

            e = XreadAllTypesPrimary(boole);

            if (boole && token.tokenType == Tokens.IS)
            {
                read();

                if (token.tokenType == Tokens.NOT)
                {
                    read();

                    not = !not;
                }

                if (token.tokenType == Tokens.TRUE)
                {
                    read();
                }
                else if (token.tokenType == Tokens.FALSE)
                {
                    read();

                    not = !not;
                }
                else if (token.tokenType == Tokens.UNKNOWN)
                {
                    read();

                    unknown = true;
                }
                else
                {
                    throw unexpectedToken();
                }
            }

            if (unknown)
            {
                e = new ExpressionLogical(OpTypes.IS_NULL, e);
            }
            else if (minus)
            {
                e = new ExpressionArithmetic(OpTypes.NEGATE, e);
            }
            else if (not)
            {
                e = new ExpressionLogical(OpTypes.NOT, e);
            }

            return e;
        }

        Expression XreadStringValueExpression()
        {

            return XreadCharacterValueExpression();

            //        XreadBinaryValueExpression();
        }

        Expression XreadCharacterValueExpression()
        {

            Expression e = XreadCharacterPrimary();
            SchemaObject collation = readCollateClauseOrNull();

            while (token.tokenType == Tokens.CONCAT)
            {
                read();

                Expression a = e;

                e = XreadCharacterPrimary();
                collation = readCollateClauseOrNull();
                e = new ExpressionArithmetic(OpTypes.CONCAT, a, e);
            }

            return e;
        }

        Expression XreadCharacterPrimary()
        {

            switch (token.tokenType)
            {

                case Tokens.SUBSTRING:

                //            case Token.SUBSTRING_REGEX :
                case Tokens.LOWER:
                case Tokens.UPPER:

                //            case Token.TRANSLATE_REGEX :
                case Tokens.TRIM:
                case Tokens.OVERLAY:

                    //            case Token.NORMALIZE :
                    FunctionSQL function =
                        FunctionSQL.newSQLFunction(token.tokenString,
                                                   compileContext);
                    Expression e = readSQLFunction(function);

                    if (e != null)
                    {
                        return e;
                    }
                    break;
              
            }

            return XreadValueExpressionPrimary();
        }

        Expression XreadNumericPrimary()
        {

            switch (token.tokenType)
            {

                case Tokens.POSITION:

                //            case Token.OCCURRENCES_REGEX :
                //            case Token.POSITION_REGEX :
                case Tokens.EXTRACT:
                case Tokens.CHAR_LENGTH:
                case Tokens.CHARACTER_LENGTH:
                case Tokens.OCTET_LENGTH:
                case Tokens.CARDINALITY:
                case Tokens.ABS:
                case Tokens.MOD:
                case Tokens.LN:
                case Tokens.EXP:
                case Tokens.POWER:
                case Tokens.SQRT:
                case Tokens.FLOOR:
                case Tokens.CEILING:
                case Tokens.CEIL:

                    //            case Token.WIDTH_BUCKET :
                    FunctionSQL function =
                        FunctionSQL.newSQLFunction(token.tokenString,
                                                   compileContext);

                    if (function == null)
                    {
                        throw base.unexpectedToken();
                    }

                    Expression e = readSQLFunction(function);

                    if (e != null)
                    {
                        return e;
                    }
                    break;
        
            }

            return XreadValueExpressionPrimary();
        }

        Expression XreadNumericValueExpression()
        {

            Expression e = XreadTerm();

            while (true)
            {
                int type;

                if (token.tokenType == Tokens.PLUS)
                {
                    type = OpTypes.ADD;
                }
                else if (token.tokenType == Tokens.MINUS)
                {
                    type = OpTypes.SUBTRACT;
                }
                else
                {
                    break;
                }

                read();

                Expression a = e;

                e = XreadTerm();
                e = new ExpressionArithmetic(type, a, e);
            }

            return e;
        }

        Expression XreadTerm()
        {

            Expression e = XreadFactor();
            int type;

            while (true)
            {
                if (token.tokenType == Tokens.ASTERISK)
                {
                    type = OpTypes.MULTIPLY;
                }
                else if (token.tokenType == Tokens.DIVIDE)
                {
                    type = OpTypes.DIVIDE;
                }
                else
                {
                    break;
                }

                read();

                Expression a = e;

                e = XreadFactor();

                if (e == null)
                {
                    throw unexpectedToken();
                }

                e = new ExpressionArithmetic(type, a, e);
            }

            return e;
        }

        Expression XreadFactor()
        {

            Expression e;
            bool minus = false;

            if (token.tokenType == Tokens.PLUS)
            {
                read();
            }
            else if (token.tokenType == Tokens.MINUS)
            {
                read();

                minus = true;
            }

            e = XreadNumericPrimary();

            if (e == null)
            {
                return null;
            }

            if (minus)
            {
                e = new ExpressionArithmetic(OpTypes.NEGATE, e);
            }

            return e;
        }

        Expression XreadDatetimeValueExpression()
        {

            Expression e = XreadDateTimeIntervalTerm();

            while (true)
            {
                int type;

                if (token.tokenType == Tokens.PLUS)
                {
                    type = OpTypes.ADD;
                }
                else if (token.tokenType == Tokens.MINUS)
                {
                    type = OpTypes.SUBTRACT;
                }
                else
                {
                    break;
                }

                read();

                Expression a = e;

                e = XreadDateTimeIntervalTerm();
                e = new ExpressionArithmetic(type, a, e);
            }

            return e;
        }

       public Expression XreadIntervalValueExpression()
        {

            Expression e = XreadDateTimeIntervalTerm();

            while (true)
            {
                int type;

                if (token.tokenType == Tokens.PLUS)
                {
                    type = OpTypes.ADD;
                }
                else if (token.tokenType == Tokens.MINUS)
                {
                    type = OpTypes.SUBTRACT;
                }
                else
                {
                    break;
                }

                read();

                Expression a = e;

                e = XreadDateTimeIntervalTerm();
                e = new ExpressionArithmetic(type, a, e);
            }

            return e;
        }

       public Expression XreadDateTimeIntervalTerm()
        {

            switch (token.tokenType)
            {

                case Tokens.CURRENT_DATE:
                case Tokens.CURRENT_TIME:
                case Tokens.CURRENT_TIMESTAMP:
                case Tokens.LOCALTIME:
                case Tokens.LOCALTIMESTAMP:

                //
                case Tokens.ABS:
                    FunctionSQL function =
                        FunctionSQL.newSQLFunction(token.tokenString,
                                                   compileContext);

                    if (function == null)
                    {
                        throw base.unexpectedToken();
                    }

                    return readSQLFunction(function);

                
            }

            return XreadValueExpressionPrimary();
        }

        // returns null
        public Expression XreadDateTimeValueFunctionOrNull()
        {

            FunctionSQL function = null;

            switch (token.tokenType)
            {

                case Tokens.CURRENT_DATE:
                case Tokens.CURRENT_TIME:
                case Tokens.CURRENT_TIMESTAMP:
                case Tokens.LOCALTIME:
                case Tokens.LOCALTIMESTAMP:
                    function = FunctionSQL.newSQLFunction(token.tokenString,
                                                          compileContext);
                    break;

                case Tokens.NOW:
                case Tokens.TODAY:
                    function = FunctionCustom.newCustomFunction(token.tokenString,
                            token.tokenType);
                    break;

                default:
                    return null;
            }

            if (function == null)
            {
                throw base.unexpectedToken();
            }

            return readSQLFunction(function);
        }

        public Expression XreadBooleanValueExpression()
        {

            try
            {
                Expression e = XreadBooleanTermOrNull();

                if (e == null)
                {
                    throw Error.error(ErrorCode.X_42568);
                }

                while (true)
                {
                    int type;

                    if (token.tokenType == Tokens.OR)
                    {
                        type = OpTypes.OR;
                    }
                    else
                    {
                        break;
                    }

                    read();

                    Expression a = e;

                    e = XreadBooleanTermOrNull();
                    e = new ExpressionLogical(type, a, e);
                }

                if (e == null)
                {
                    throw Error.error(ErrorCode.X_42568);
                }

                return e;
            }
            catch (CoreException ex)
            {
                ex.setLevel(compileContext.subQueryDepth);

                if (lastError == null || lastError.getLevel() < ex.getLevel())
                {
                    lastError = ex;
                }

                throw lastError;
            }
        }

       public Expression XreadBooleanTermOrNull()
        {

            Expression e = XreadBooleanFactorOrNull();
            int type;

            while (true)
            {
                if (token.tokenType == Tokens.AND)
                {
                    type = OpTypes.AND;
                }
                else
                {
                    break;
                }

                read();

                Expression a = e;

                e = XreadBooleanFactorOrNull();
                e = new ExpressionLogical(type, a, e);
            }

            return e;
        }

        public Expression XreadBooleanFactorOrNull()
        {

            Expression e;
            bool not = false;
            bool unknown = false;

            if (token.tokenType == Tokens.NOT)
            {
                read();

                not = true;
            }

            e = XreadBooleanPrimaryOrNull();

            if (e == null)
            {
                return null;
            }

            if (token.tokenType == Tokens.IS)
            {
                read();

                if (token.tokenType == Tokens.NOT)
                {
                    read();

                    not = !not;
                }

                if (token.tokenType == Tokens.TRUE)
                {
                    read();
                }
                else if (token.tokenType == Tokens.FALSE)
                {
                    not = !not;

                    read();
                }
                else if (token.tokenType == Tokens.UNKNOWN)
                {
                    unknown = true;

                    read();
                }
                else
                {
                    throw unexpectedToken();
                }
            }

            if (unknown)
            {
                e = new ExpressionLogical(OpTypes.IS_NULL, e);
            }

            if (not)
            {
                e = new ExpressionLogical(OpTypes.NOT, e);
            }

            return e;
        }

        // <bool primary> ::= <predicate> | <bool predicand>
        public Expression XreadBooleanPrimaryOrNull()
        {

            Expression e = null;
            int position;

            switch (token.tokenType)
            {

                case Tokens.EXISTS:
                case Tokens.UNIQUE:
                    return XreadPredicate();

                case Tokens.ROW:
                    read();
                    readThis(Tokens.OPENBRACKET);

                    e = XreadRowElementList(true);

                    readThis(Tokens.CLOSEBRACKET);
                    break;

                default:
                    position = getPosition();

                    try
                    {
                        e = XreadAllTypesCommonValueExpression(false);
                    }
                    catch (CoreException ex)
                    {
                        ex.setLevel(compileContext.subQueryDepth);

                        if (lastError == null
                                || lastError.getLevel() < ex.getLevel())
                        {
                            lastError = ex;
                        }

                        rewind(position);
                    }
                    break;
            }

            if (e == null && token.tokenType == Tokens.OPENBRACKET)
            {
                read();

                position = getPosition();

                try
                {
                    e = XreadRowElementList(true);

                    readThis(Tokens.CLOSEBRACKET);
                }
                catch (CoreException ex)
                {
                    ex.setLevel(compileContext.subQueryDepth);

                    if (lastError == null
                            || lastError.getLevel() < ex.getLevel())
                    {
                        lastError = ex;
                    }

                    rewind(position);

                    e = XreadBooleanValueExpression();

                    readThis(Tokens.CLOSEBRACKET);
                }
            }

            if (e != null)
            {
                e = XreadPredicateRightPart(e);
            }

            return e;
        }

        // similar to <value expression primary>
        public Expression XreadBooleanPredicand()
        {

            Expression e;

            if (token.tokenType == Tokens.OPENBRACKET)
            {
                read();

                e = XreadBooleanValueExpression();

                readThis(Tokens.CLOSEBRACKET);

                return e;
            }
            else
            {
                return XreadSimpleValueExpressionPrimary();
            }
        }

        public Expression XreadPredicate()
        {

            switch (token.tokenType)
            {

                case Tokens.EXISTS:
                    {
                        read();

                        Expression s = XreadTableSubqueryForPredicate(OpTypes.EXISTS);

                        return new ExpressionLogical(OpTypes.EXISTS, s);
                    }
                case Tokens.UNIQUE:
                    {
                        read();

                        Expression s = XreadTableSubqueryForPredicate(OpTypes.UNIQUE);

                        return new ExpressionLogical(OpTypes.UNIQUE, s);
                    }
                default:
                    {
                        Expression a = XreadRowValuePredicand();

                        return XreadPredicateRightPart(a);
                    }
            }
        }

        public Expression XreadPredicateRightPart(Expression l)
        {

            bool hasNot = false;
            ExpressionLogical e = null;
            Expression r;

            if (token.tokenType == Tokens.NOT)
            {
                read();

                hasNot = true;
            }

            switch (token.tokenType)
            {

                case Tokens.IS:
                    {
                        if (hasNot)
                        {
                            throw unexpectedToken();
                        }

                        read();

                        if (token.tokenType == Tokens.NOT)
                        {
                            hasNot = true;

                            read();
                        }

                        if (token.tokenType == Tokens.DISTINCT)
                        {
                            read();
                            readThis(Tokens.FROM);

                            r = XreadRowValuePredicand();
                            e = new ExpressionLogical(OpTypes.NOT_DISTINCT, l, r);
                            hasNot = !hasNot;

                            break;
                        }

                        if (token.tokenType == Tokens.NULL
                                || token.tokenType == Tokens.UNKNOWN)
                        {
                            read();

                            e = new ExpressionLogical(OpTypes.IS_NULL, l);

                            break;
                        }

                        throw unexpectedToken();
                    }
                case Tokens.LIKE:
                    {
                        e = XreadLikePredicateRightPart(l);
                        e.noOptimisation = isCheckOrTriggerCondition;

                        break;
                    }
                case Tokens.BETWEEN:
                    {
                        e = XreadBetweenPredicateRightPart(l);

                        break;
                    }
                case Tokens.IN:
                    {
                        e = XreadInPredicateRightPart(l);
                        e.noOptimisation = isCheckOrTriggerCondition;

                        break;
                    }
                case Tokens.OVERLAPS:
                    {
                        if (hasNot)
                        {
                            throw unexpectedToken();
                        }

                        e = XreadOverlapsPredicateRightPart(l);

                        break;
                    }
                case Tokens.EQUALS:
                case Tokens.GREATER_EQUALS:
                case Tokens.GREATER:
                case Tokens.LESS:
                case Tokens.LESS_EQUALS:
                case Tokens.NOT_EQUALS:
                    {
                        if (hasNot)
                        {
                            throw unexpectedToken();
                        }

                        int type = getExpressionType(token.tokenType);

                        read();

                        switch (token.tokenType)
                        {

                            case Tokens.ANY:
                            case Tokens.SOME:
                            case Tokens.ALL:
                                e = XreadQuantifiedComparisonRightPart(type, l);
                                break;

                            default:
                                {
                                    Expression row = XreadRowValuePredicand();

                                    e = new ExpressionLogical(type, l, row);

                                    break;
                                }
                        }

                        break;
                    }
                case Tokens.MATCH:
                    {
                        e = XreadMatchPredicateRightPart(l);

                        break;
                    }
                default:
                    {
                        if (hasNot)
                        {
                            throw unexpectedToken();
                        }

                        return l;
                    }
            }

            if (hasNot)
            {
                e = new ExpressionLogical(OpTypes.NOT, e);
            }

            return e;
        }

        private ExpressionLogical XreadBetweenPredicateRightPart(
                 Expression a)
        {

            bool symmetric = false;

            read();

            if (token.tokenType == Tokens.ASYMMETRIC)
            {
                read();
            }
            else if (token.tokenType == Tokens.SYMMETRIC)
            {
                symmetric = true;

                read();
            }

            Expression left = XreadRowValuePredicand();

            readThis(Tokens.AND);

            Expression right = XreadRowValuePredicand();

            if (a.isParam() && left.isParam())
            {
                throw Error.error(ErrorCode.X_42567);
            }

            if (a.isParam() && right.isParam())
            {
                throw Error.error(ErrorCode.X_42567);
            }

            Expression l = new ExpressionLogical(OpTypes.GREATER_EQUAL, a, left);
            Expression r = new ExpressionLogical(OpTypes.SMALLER_EQUAL, a, right);
            ExpressionLogical leftToRight = new ExpressionLogical(OpTypes.AND, l,
                r);

            if (symmetric)
            {
                l = new ExpressionLogical(OpTypes.SMALLER_EQUAL, a, left);
                r = new ExpressionLogical(OpTypes.GREATER_EQUAL, a, right);

                Expression rightToLeft = new ExpressionLogical(OpTypes.AND, l, r);

                return new ExpressionLogical(OpTypes.OR, leftToRight, rightToLeft);
            }
            else
            {
                return leftToRight;
            }
        }

        private ExpressionLogical XreadQuantifiedComparisonRightPart(int exprType,
                Expression l)
        {

            int tokenT = token.tokenType;
            int exprSubType = 0;
            Expression e;

            switch (token.tokenType)
            {

                case Tokens.ANY:
                case Tokens.SOME:
                    exprSubType = OpTypes.ANY_QUANTIFIED;
                    break;

                case Tokens.ALL:
                    exprSubType = OpTypes.ALL_QUANTIFIED;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "ParserDQL");
            }

            read();
            readThis(Tokens.OPENBRACKET);

            int position = getPosition();

            readOpenBrackets();

            switch (token.tokenType)
            {

                case Tokens.TABLE:
                case Tokens.VALUES:
                case Tokens.SELECT:
                    rewind(position);

                    SubQuery sq = XreadSubqueryBody(false, OpTypes.IN);

                    e = new Expression(OpTypes.TABLE_SUBQUERY, sq);

                    readThis(Tokens.CLOSEBRACKET);
                    break;

                default:
                    rewind(position);

                    e = readAggregateExpression(tokenT);

                    readThis(Tokens.CLOSEBRACKET);
                    break;
            }

            ExpressionLogical r = new ExpressionLogical(exprType, l, e);

            r.exprSubType = exprSubType;

            return r;
        }

        private ExpressionLogical XreadInPredicateRightPart(Expression l)
        {

            int degree = l.getDegree();
            Expression e = null;

            read();
            readThis(Tokens.OPENBRACKET);

            int position = getPosition();

            readOpenBrackets();

            switch (token.tokenType)
            {

                case Tokens.TABLE:
                case Tokens.VALUES:
                case Tokens.SELECT:
                    {
                        rewind(position);

                        SubQuery sq = XreadSubqueryBody(false, OpTypes.IN);

                        e = new Expression(OpTypes.TABLE_SUBQUERY, sq);

                        readThis(Tokens.CLOSEBRACKET);

                        break;
                    }
                default:
                    {
                        rewind(position);

                        e = XreadInValueListConstructor(degree);

                        readThis(Tokens.CLOSEBRACKET);

                        break;
                    }
            }

            ExpressionLogical r;

            if (isCheckOrTriggerCondition)
            {
                r = new ExpressionLogical(OpTypes.IN, l, e);
            }
            else
            {
                r = new ExpressionLogical(OpTypes.EQUAL, l, e);
                r.exprSubType = OpTypes.ANY_QUANTIFIED;
            }

            return r;
        }

        Expression XreadInValueList(int degree)
        {

            EfzArrayList list = new EfzArrayList();

            while (true)
            {
                Expression e = XreadValueExpression();

                if (e.getType() != OpTypes.ROW)
                {
                    e = new Expression(OpTypes.ROW, new Expression[] { e });
                }

                list.add(e);

                if (token.tokenType == Tokens.COMMA)
                {
                    read();

                    continue;
                }

                break;
            }

            Expression[] array = new Expression[list.size()];

            list.toArray(array);

            Expression ee = new Expression(OpTypes.TABLE, array);

            for (int i = 0; i < array.Length; i++)
            {
                if (array[i].getType() != OpTypes.ROW)
                {
                    array[i] = new Expression(OpTypes.ROW,
                                              new Expression[] { array[i] });
                }

                Expression[] args = array[i].nodes;

                if (args.Length != degree)
                {

                    // SQL error message
                    throw unexpectedToken();
                }

                for (int j = 0; j < degree; j++)
                {
                    if (args[j].getType() == OpTypes.ROW)
                    {

                        // SQL error message
                        throw unexpectedToken();
                    }
                }
            }

            return ee;
        }

        private ExpressionLogical XreadLikePredicateRightPart(Expression a)
        {

            read();

            Expression b = XreadStringValueExpression();
            Expression escape = null;

            if (token.tokenString.Equals(Tokens.T_ESCAPE))
            {
                read();

                escape = XreadStringValueExpression();
            }

            return new ExpressionLike(a, b, escape,
                                      this.isCheckOrTriggerCondition);
        }

        private ExpressionLogical XreadMatchPredicateRightPart(Expression a)
        {

            bool isUnique = false;
            int matchType = OpTypes.MATCH_SIMPLE;

            read();

            if (token.tokenType == Tokens.UNIQUE)
            {
                read();

                isUnique = true;
            }

            if (token.tokenType == Tokens.SIMPLE)
            {
                read();

                matchType = isUnique ? OpTypes.MATCH_UNIQUE_SIMPLE
                                     : OpTypes.MATCH_SIMPLE;
            }
            else if (token.tokenType == Tokens.PARTIAL)
            {
                read();

                matchType = isUnique ? OpTypes.MATCH_UNIQUE_PARTIAL
                                     : OpTypes.MATCH_PARTIAL;
            }
            else if (token.tokenType == Tokens.FULL)
            {
                read();

                matchType = isUnique ? OpTypes.MATCH_UNIQUE_FULL
                                     : OpTypes.MATCH_FULL;
            }

            int mode = isUnique ? OpTypes.TABLE_SUBQUERY
                                       : OpTypes.IN;
            Expression s = XreadTableSubqueryForPredicate(mode);

            return new ExpressionLogical(matchType, a, s);
        }

        private ExpressionLogical XreadOverlapsPredicateRightPart(Expression l)
        {

            if (l.getType() != OpTypes.ROW)
            {
                throw Error.error(ErrorCode.X_42564);
            }

            if (l.nodes.Length != 2)
            {
                throw Error.error(ErrorCode.X_42564);
            }

            read();

            if (token.tokenType != Tokens.OPENBRACKET)
            {
                throw unexpectedToken();
            }

            Expression r = XreadRowValuePredicand();

            if (r.nodes.Length != 2)
            {
                throw Error.error(ErrorCode.X_42564);
            }

            return new ExpressionLogical(OpTypes.OVERLAPS, l, r);
        }

        Expression XreadRowValueExpression()
        {

            Expression e = XreadExplicitRowValueConstructorOrNull();

            if (e != null)
            {
                return e;
            }

            return XreadRowValueSpecialCase();
        }

        Expression XreadTableRowValueConstructor()
        {

            Expression e = XreadExplicitRowValueConstructorOrNull();

            if (e != null)
            {
                return e;
            }

            return XreadRowValueSpecialCase();
        }

        //  union of <row value expression> |
        // <bool predicand> | <non parenthesized value expression primary> |
        //  translated to <explicit row value constructor>
        // <value expression primary> | <non parenthesized value expression primary> |
        public Expression XreadRowValuePredicand()
        {
            return XreadRowOrCommonValueExpression();
        }

        public Expression XreadRowValueSpecialCase()
        {
            return XreadSimpleValueExpressionPrimary();
        }

        // <row value constructor>
        // ISSUE - XreadCommonValueExpression and XreadBooleanValueExpression should merge
        Expression XreadRowValueConstructor()
        {

            Expression e;

            e = XreadExplicitRowValueConstructorOrNull();

            if (e != null)
            {
                return e;
            }

            e = XreadRowOrCommonValueExpression();

            if (e != null)
            {
                return e;
            }

            return XreadBooleanValueExpression();
        }

        // returns null
        // must be called in conjusnction with <parenthesized ..
        Expression XreadExplicitRowValueConstructorOrNull()
        {

            Expression e;

            switch (token.tokenType)
            {

                case Tokens.OPENBRACKET:
                    {
                        read();

                        int position = getPosition();
                        int brackets = readOpenBrackets();

                        switch (token.tokenType)
                        {

                            case Tokens.TABLE:
                            case Tokens.VALUES:
                            case Tokens.SELECT:
                                rewind(position);

                                SubQuery sq = XreadSubqueryBody(false,
                                                                OpTypes.ROW_SUBQUERY);

                                readThis(Tokens.CLOSEBRACKET);

                                return new Expression(OpTypes.ROW_SUBQUERY, sq);

                            default:
                                rewind(position);

                                e = XreadRowElementList(true);

                                readThis(Tokens.CLOSEBRACKET);

                                return e;
                        }
                    }
                case Tokens.ROW:
                    {
                        read();
                        readThis(Tokens.OPENBRACKET);

                        e = XreadRowElementList(false);

                        readThis(Tokens.CLOSEBRACKET);

                        return e;
                    }
            }

            return null;
        }

        Expression XreadRowElementList(bool multiple)
        {

            Expression e;
            EfzArrayList list = new EfzArrayList();

            while (true)
            {
                e = XreadValueExpression();

                list.add(e);

                if (token.tokenType == Tokens.COMMA)
                {
                    read();

                    continue;
                }

                if (multiple && list.size() == 1)
                {
                    return e;
                }

                break;
            }

            Expression[] array = new Expression[list.size()];

            list.toArray(array);

            return new Expression(OpTypes.ROW, array);
        }

        Expression XreadCurrentCollationSpec()
        {
            throw Error.error(ErrorCode.X_0A000);
        }

        Expression XreadRowSubquery()
        {

            readThis(Tokens.OPENBRACKET);

            SubQuery sq = XreadSubqueryBody(false, OpTypes.ROW_SUBQUERY);

            readThis(Tokens.CLOSEBRACKET);

            return new Expression(OpTypes.ROW_SUBQUERY, sq);
        }

        Expression XreadTableSubqueryForPredicate(int mode)
        {

            readThis(Tokens.OPENBRACKET);

            SubQuery sq = XreadSubqueryBody(false, mode);

            readThis(Tokens.CLOSEBRACKET);

            return new Expression(OpTypes.TABLE_SUBQUERY, sq);
        }

        Expression XreadTableSubqueryOrJoinedTable()
        {

            bool joinedTable = false;
            int position;

            readThis(Tokens.OPENBRACKET);

            position = getPosition();

            readOpenBrackets();

            switch (token.tokenType)
            {

                case Tokens.TABLE:
                case Tokens.VALUES:
                case Tokens.SELECT:
                case Tokens.WITH:
                    break;

                default:
                    joinedTable = true;
                    break;
            }

            rewind(position);

            if (joinedTable)
            {
                SubQuery sq = XreadJoinedTableAsSubquery();

                readThis(Tokens.CLOSEBRACKET);

                return new Expression(OpTypes.TABLE_SUBQUERY, sq);
            }
            else
            {
                SubQuery sq = XreadTableSubqueryBody();

                readThis(Tokens.CLOSEBRACKET);

                return new Expression(OpTypes.TABLE_SUBQUERY, sq);
            }
        }

        SubQuery XreadJoinedTableAsSubquery()
        {

            compileContext.subQueryDepth++;

            QueryExpression queryExpression = XreadJoinedTable();

            queryExpression.resolve(session);

            if (((QuerySpecification)queryExpression).rangeVariables.Length < 2)
            {
                throw unexpectedTokenRequire(Tokens.T_JOIN);
            }

            SubQuery sq = new SubQuery(database, compileContext.subQueryDepth,
                                       queryExpression, OpTypes.TABLE_SUBQUERY);

            sq.prepareTable(session);

            compileContext.subQueryDepth--;

            compileContext.subQueryList.add(sq);

            return sq;
        }

        public QueryExpression XreadJoinedTable()
        {

            QuerySpecification select = new QuerySpecification(compileContext);
            Expression e = new ExpressionColumn(OpTypes.MULTICOLUMN);

            select.addSelectColumnExpression(e);
            XreadTableReference(select);

            return select;
        }

        public SubQuery XreadTableSubqueryBody()
        {

            SubQuery sq = XreadSubqueryBody(true, OpTypes.TABLE_SUBQUERY);

            /*
                    Select   select = sq.queryExpression.getMainSelect();

                    for (int i = 0; i < select.indexLimitVisible; i++) {
                        String colname = select.exprColumns[i].getAlias();

                        if (colname == null || colname.Length() == 0) {

                            // fredt - this does not guarantee the uniqueness of column
                            // names but addColumns() will throw later if names are not unique.
                            colname = HsqlNameManager.getAutoColumnNameString(i);

                            select.exprColumns[i].setAlias(colname, false);
                        }
                    }
            */
            sq.prepareTable(session);

            return sq;
        }

        public SubQuery XreadSubqueryBody(bool resolve, int mode)
        {

            compileContext.subQueryDepth++;

            QueryExpression queryExpression = XreadQueryExpression();

            if (resolve)
            {
                queryExpression.resolve(session);
            }
            else
            {
                queryExpression.resolveReferences(session);
            }

            SubQuery sq = new SubQuery(database, compileContext.subQueryDepth,
                                       queryExpression, mode);

            compileContext.subQueryList.add(sq);

            compileContext.subQueryDepth--;

            return sq;
        }

        public SubQuery XreadViewSubquery(View view)
        {

            compileContext.subQueryDepth++;

            QueryExpression queryExpression;

            try
            {
                queryExpression = XreadQueryExpression();
            }
            catch (CoreException )
            {
                queryExpression = XreadJoinedTable();
            }

            queryExpression.setAsTopLevel();
            queryExpression.setView(view);
            queryExpression.resolve(session);

            SubQuery sq = new SubQuery(database, compileContext.subQueryDepth,
                                       queryExpression, view);

            compileContext.subQueryList.add(sq);

            compileContext.subQueryDepth--;

            return sq;
        }

        // Additional Common Elements
        // returns null
        SchemaObject readCollateClauseOrNull()
        {

            if (token.tokenType == Tokens.COLLATE)
            {
                read();

                SchemaObject collation =
                    database.schemaManager.getSchemaObject(token.namePrefix,
                        token.tokenString, SchemaObjectTypes.COLLATION);

                return collation;
            }

            return null;
        }

        public Expression readRow()
        {

            Expression r = null;

            while (true)
            {
                Expression e = XreadValueExpressionWithContext();

                if (r == null)
                {
                    r = e;
                }
                else if (r.getType() == OpTypes.ROW)
                {
                    if (e.getType() == OpTypes.ROW
                            && r.nodes[0].getType() != OpTypes.ROW)
                    {
                        r = new Expression(OpTypes.ROW, new Expression[] {
                        r, e
                    });
                    }
                    else
                    {
                        r.nodes = (Expression[])ArrayUtil.resizeArray(r.nodes,
                                r.nodes.Length + 1);
                        r.nodes[r.nodes.Length - 1] = e;
                    }
                }
                else
                {
                    r = new Expression(OpTypes.ROW, new Expression[] {
                    r, e
                });
                }

                if (token.tokenType != Tokens.COMMA)
                {
                    break;
                }

                read();
            }

            return r;
        }

        public Expression XreadContextuallyTypedTable(int degree)
        {

            Expression e = readRow();
            Expression[] list = e.nodes;
            bool isTable = false;

            if (degree == 1)
            {
                if (e.getType() == OpTypes.ROW)
                {
                    e.opType = OpTypes.TABLE;

                    for (int i = 0; i < list.Length; i++)
                    {
                        if (list[i].getType() != OpTypes.ROW)
                        {
                            list[i] = new Expression(OpTypes.ROW,
                                                     new Expression[] { list[i] });
                        }
                        else if (list[i].nodes.Length != degree)
                        {
                            throw Error.error(ErrorCode.X_42564);
                        }
                    }

                    return e;
                }
                else
                {
                    e = new Expression(OpTypes.ROW, new Expression[] { e });
                    e = new Expression(OpTypes.TABLE, new Expression[] { e });

                    return e;
                }
            }

            if (e.getType() != OpTypes.ROW)
            {
                throw Error.error(ErrorCode.X_42564);
            }

            for (int i = 0; i < list.Length; i++)
            {
                if (list[i].getType() == OpTypes.ROW)
                {
                    isTable = true;

                    break;
                }
            }

            if (isTable)
            {
                e.opType = OpTypes.TABLE;

                for (int i = 0; i < list.Length; i++)
                {
                    if (list[i].getType() != OpTypes.ROW)
                    {
                        throw Error.error(ErrorCode.X_42564);
                    }

                    Expression[] args = list[i].nodes;

                    if (args.Length != degree)
                    {
                        throw Error.error(ErrorCode.X_42564);
                    }

                    for (int j = 0; j < degree; j++)
                    {
                        if (args[j].getType() == OpTypes.ROW)
                        {
                            throw Error.error(ErrorCode.X_42564);
                        }
                    }
                }
            }
            else
            {
                if (list.Length != degree)
                {
                    throw Error.error(ErrorCode.X_42564);
                }

                e = new Expression(OpTypes.TABLE, new Expression[] { e });
            }

            return e;
        }

        private Expression XreadInValueListConstructor(int degree)
        {

            compileContext.subQueryDepth++;

            Expression e = XreadInValueList(degree);
            SubQuery sq = new SubQuery(database, compileContext.subQueryDepth, e,
                                       OpTypes.IN);

            compileContext.subQueryList.add(sq);

            compileContext.subQueryDepth--;

            return e;
        }

        private SubQuery XreadRowValueExpressionList()
        {

            compileContext.subQueryDepth++;

            Expression e = XreadRowValueExpressionListBody();
            EfzList unresolved =
                e.resolveColumnReferences(RangeVariable.emptyArray, null);

            ExpressionColumn.checkColumnsResolved(unresolved);
            e.resolveTypes(session, null);
            e.prepareTable(session, null, e.nodes[0].nodes.Length);

            SubQuery sq = new SubQuery(database, compileContext.subQueryDepth, e,
                                       OpTypes.TABLE);

            sq.prepareTable(session);
            compileContext.subQueryList.add(sq);

            compileContext.subQueryDepth--;

            return sq;
        }

        Expression XreadRowValueExpressionListBody()
        {

            Expression r = null;

            while (true)
            {
                int brackets = readOpenBrackets();
                Expression e = readRow();

                readCloseBrackets(brackets);

                if (r == null)
                {
                    r = new Expression(OpTypes.ROW, new Expression[] { e });
                }
                else
                {
                    r.nodes = (Expression[])ArrayUtil.resizeArray(r.nodes,
                            r.nodes.Length + 1);
                    r.nodes[r.nodes.Length - 1] = e;
                }

                if (token.tokenType != Tokens.COMMA)
                {
                    break;
                }

                read();
            }

            Expression[] list = r.nodes;
            int degree = 1;

            if (list[0].getType() == OpTypes.ROW)
            {
                degree = list[0].nodes.Length;
            }

            r.opType = OpTypes.TABLE;

            for (int i = 0; i < list.Length; i++)
            {
                if (list[i].getType() == OpTypes.ROW)
                {
                    if (list[i].nodes.Length != degree)
                    {
                        throw Error.error(ErrorCode.X_42564);
                    }
                }
                else
                {
                    if (degree != 1)
                    {
                        throw Error.error(ErrorCode.X_42564);
                    }

                    list[i] = new Expression(OpTypes.ROW,
                                             new Expression[] { list[i] });
                }
            }

            return r;
        }

        Expression readCaseExpression()
        {

            Expression predicand = null;

            read();

            if (token.tokenType != Tokens.WHEN)
            {
                predicand = XreadRowValuePredicand();
            }

            return readCaseWhen(predicand);
        }

        /**
         * Reads part of a CASE .. WHEN  expression
         */
        private Expression readCaseWhen(Expression l)
        {

            readThis(Tokens.WHEN);

            Expression condition = null;

            if (l == null)
            {
                condition = XreadBooleanValueExpression();
            }
            else
            {
                while (true)
                {
                    Expression newCondition = XreadPredicateRightPart(l);

                    if (l == newCondition)
                    {
                        newCondition =
                            new ExpressionLogical(l, XreadRowValuePredicand());
                    }

                    if (condition == null)
                    {
                        condition = newCondition;
                    }
                    else
                    {
                        condition = new ExpressionLogical(OpTypes.OR, condition,
                                                          newCondition);
                    }

                    if (token.tokenType == Tokens.COMMA)
                    {
                        read();
                    }
                    else
                    {
                        break;
                    }
                }
            }

            readThis(Tokens.THEN);

            Expression current = XreadValueExpression();
            Expression elseExpr = null;

            if (token.tokenType == Tokens.WHEN)
            {
                elseExpr = readCaseWhen(l);
            }
            else if (token.tokenType == Tokens.ELSE)
            {
                read();

                elseExpr = XreadValueExpression();

                readThis(Tokens.END);
                readIfThis(Tokens.CASE);
            }
            else
            {
                elseExpr = new ExpressionValue((Object)null, SqlType.SQL_ALL_TYPES);

                readThis(Tokens.END);
                readIfThis(Tokens.CASE);
            }

            Expression alternatives = new ExpressionOp(OpTypes.ALTERNATIVE,
                current, elseExpr);
            Expression casewhen = new ExpressionOp(OpTypes.CASEWHEN, condition,
                                                   alternatives);

            return casewhen;
        }

        /**
         * reads a CASEWHEN expression
         */
        private Expression readCaseWhenExpression()
        {

            Expression l = null;

            read();
            readThis(Tokens.OPENBRACKET);

            l = XreadBooleanValueExpression();

            readThis(Tokens.COMMA);

            Expression thenelse = XreadRowValueExpression();

            readThis(Tokens.COMMA);

            thenelse = new ExpressionOp(OpTypes.ALTERNATIVE, thenelse,
                                        XreadValueExpression());
            l = new ExpressionOp(OpTypes.CASEWHEN, l, thenelse);

            readThis(Tokens.CLOSEBRACKET);

            return l;
        }

        /**
         * Reads a CAST or CONVERT expression
         */
        private Expression readCastExpression()
        {

            bool isConvert = token.tokenType == Tokens.CONVERT;

            read();
            readThis(Tokens.OPENBRACKET);

            Expression l = this.XreadValueExpressionOrNull();

            if (isConvert)
            {
                readThis(Tokens.COMMA);
            }
            else
            {
                readThis(Tokens.AS);
            }

            SqlType typeObject = readTypeDefinition(true);

            if (l.isParam())
            {
                l.setDataType(session, typeObject);
            }

            l = new ExpressionOp(l, typeObject);

            readThis(Tokens.CLOSEBRACKET);

            return l;
        }

        /**
         * reads a Column or Function expression
         */
        private Expression readColumnOrFunctionExpression()
        {

            String name = token.tokenString;
            bool isSimpleQuoted = isDelimitedSimpleName();
            String prefix = token.namePrefix;
            String prePrefix = token.namePrePrefix;
            String prePrePrefix = token.namePrePrePrefix;

            if (isUndelimitedSimpleName())
            {
                FunctionSQL function =
                    FunctionCustom.newCustomFunction(token.tokenString,
                                                     token.tokenType);

                if (function != null)
                {
                    int pos = getPosition();

                    try
                    {
                        Expression e = readSQLFunction(function);

                        if (e != null)
                        {
                            return e;
                        }
                    }
                    catch (CoreException ex)
                    {
                        ex.setLevel(compileContext.subQueryDepth);

                        if (lastError == null
                                || lastError.getLevel() < ex.getLevel())
                        {
                            lastError = ex;
                        }

                        rewind(pos);
                    }
                }
                else if (isReservedKey())
                {
                    function = FunctionSQL.newSQLFunction(name, compileContext);

                    if (function != null)
                    {
                        Expression e = readSQLFunction(function);

                        if (e != null)
                        {
                            return e;
                        }
                    }
                }
            }

            read();

            if (token.tokenType != Tokens.OPENBRACKET)
            {
                checkValidCatalogName(prePrePrefix);

                Expression column = new ExpressionColumn(prePrefix, prefix, name);

                return column;
            }

            if (prePrePrefix != null)
            {
                throw Error.error(ErrorCode.X_42551, prePrePrefix);
            }

            checkValidCatalogName(prePrefix);

            prefix = session.getSchemaName(prefix);

            RoutineSchema routineSchema =
                (RoutineSchema)database.schemaManager.findSchemaObject(name,
                    prefix, SchemaObjectTypes.FUNCTION);

            if (routineSchema == null && isSimpleQuoted)
            {
                QNameManager.QName schema =
                    database.schemaManager.getDefaultSchemaHsqlName();

                routineSchema =
                    (RoutineSchema)database.schemaManager.findSchemaObject(name,
                        schema.name, SchemaObjectTypes.FUNCTION);

                if (routineSchema == null)
                {
                    Routine.createRoutines(session, schema, name);

                    routineSchema =
                        (RoutineSchema)database.schemaManager.findSchemaObject(
                            name, schema.name, SchemaObjectTypes.FUNCTION);
                }
            }

            if (routineSchema == null)
            {
                throw Error.error(ErrorCode.X_42501, name);
            }

            EfzArrayList list = new EfzArrayList();

            readThis(Tokens.OPENBRACKET);

            if (token.tokenType == Tokens.CLOSEBRACKET)
            {
                read();
            }
            else
            {
                while (true)
                {
                    Expression e = XreadValueExpression();

                    list.add(e);

                    if (token.tokenType == Tokens.COMMA)
                    {
                        read();
                    }
                    else
                    {
                        readThis(Tokens.CLOSEBRACKET);

                        break;
                    }
                }
            }

            FunctionSQLInvoked function1 = new FunctionSQLInvoked(routineSchema);
            Expression[] arguments = new Expression[list.size()];

            list.toArray(arguments);
            function1.setArguments(arguments);
            compileContext.addRoutine(function1);

            return function1;
        }

        /**
         * Reads a NULLIF expression
         */
        private Expression readNullIfExpression()
        {

            // turn into a CASEWHEN
            read();
            readThis(Tokens.OPENBRACKET);

            Expression c = XreadValueExpression();

            readThis(Tokens.COMMA);

            Expression thenelse =
                new ExpressionOp(OpTypes.ALTERNATIVE,
                                 new ExpressionValue((Object)null, (SqlType)null),
                                 c);

            c = new ExpressionLogical(c, XreadValueExpression());
            c = new ExpressionOp(OpTypes.CASEWHEN, c, thenelse);

            readThis(Tokens.CLOSEBRACKET);

            return c;
        }

        /**
         * Reads a COALESE or IFNULL expression
         */
        private Expression readCoalesceExpression()
        {

            Expression c = null;

            // turn into a CASEWHEN
            read();
            readThis(Tokens.OPENBRACKET);

            Expression leaf = null;

            while (true)
            {
                Expression current = XreadValueExpression();

                if (leaf != null && token.tokenType == Tokens.CLOSEBRACKET)
                {
                    readThis(Tokens.CLOSEBRACKET);
                    leaf.setLeftNode(current);

                    break;
                }

                Expression condition = new ExpressionLogical(OpTypes.IS_NULL,
                    current);
                Expression alternatives = new ExpressionOp(OpTypes.ALTERNATIVE,
                    new ExpressionValue((Object)null, (SqlType)null), current);
                Expression casewhen = new ExpressionOp(OpTypes.CASEWHEN,
                                                       condition, alternatives);

                if (c == null)
                {
                    c = casewhen;
                }
                else
                {
                    leaf.setLeftNode(casewhen);
                }

                leaf = alternatives;

                readThis(Tokens.COMMA);
            }

            return c;
        }

        public Expression readSQLFunction(FunctionSQL function)
        {

            int position = getPosition();

            read();

            short[] parseList = function.parseList;

            if (parseList.Length == 0)
            {
                return function;
            }

            EfzArrayList exprList = new EfzArrayList();
            bool isOpenBracket = token.tokenType == Tokens.OPENBRACKET;

            try
            {
                readExpression(exprList, parseList, 0, parseList.Length, false);
            }
            catch (CoreException e)
            {
                if (!isOpenBracket)
                {
                    rewind(position);

                    return null;
                }

                if (function.parseListAlt == null)
                {
                    throw e;
                }

                rewind(position);
                read();

                parseList = function.parseListAlt;
                exprList = new EfzArrayList();

                readExpression(exprList, parseList, 0, parseList.Length, false);
            }

            Expression[] expr = new Expression[exprList.size()];

            exprList.toArray(expr);
            function.setArguments(expr);

            return function.getFunctionExpression();
        }

        void readExpression(EfzArrayList exprList, short[] parseList, int start,
                            int count, bool isOption)
        {

            for (int i = start; i < start + count; i++)
            {
                int exprType = parseList[i];

                switch (exprType)
                {

                    case Tokens.QUESTION:
                        {
                            Expression e = null;

                            e = XreadAllTypesCommonValueExpression(false);

                            exprList.add(e);

                            continue;
                        }
                    case Tokens.X_POS_INTEGER:
                        {
                            Expression e = null;
                            int value = readIntegerObject();

                            if (value < 0)
                            {
                                throw Error.error(ErrorCode.X_42592);
                            }

                            e = new ExpressionValue(value, SqlType.SQL_INTEGER);

                            exprList.add(e);

                            continue;
                        }
                    case Tokens.X_OPTION:
                        {
                            i++;

                            int expressionCount = exprList.size();
                            int position = getPosition();
                            int elementCount = parseList[i++];
                            int initialExprIndex = exprList.size();

                            try
                            {
                                readExpression(exprList, parseList, i, elementCount,
                                               true);
                            }
                            catch (CoreException ex)
                            {
                                ex.setLevel(compileContext.subQueryDepth);

                                if (lastError == null
                                        || lastError.getLevel() < ex.getLevel())
                                {
                                    lastError = ex;
                                }

                                rewind(position);
                                exprList.setSize(expressionCount);

                                for (int j = i; j < i + elementCount; j++)
                                {
                                    if (parseList[j] == Tokens.QUESTION
                                            || parseList[j] == Tokens.X_KEYSET
                                            || parseList[j] == Tokens.X_POS_INTEGER)
                                    {
                                        exprList.add(null);
                                    }
                                }

                                i += elementCount - 1;

                                continue;
                            }

                            if (initialExprIndex == exprList.size())
                            {
                                exprList.add(null);
                            }

                            i += elementCount - 1;

                            continue;
                        }
                    case Tokens.X_REPEAT:
                        {
                            i++;

                            int elementCount = parseList[i++];
                            int parseIndex = i;

                            while (true)
                            {
                                int initialExprIndex = exprList.size();

                                readExpression(exprList, parseList, parseIndex,
                                               elementCount, true);

                                if (exprList.size() == initialExprIndex)
                                {
                                    break;
                                }
                            }

                            i += elementCount - 1;

                            continue;
                        }
                    case Tokens.X_KEYSET:
                        {
                            int elementCount = parseList[++i];
                            Expression e = null;

                            if (ArrayUtil.find(parseList, token.tokenType, i
                                               + 1, elementCount) == -1)
                            {
                                if (!isOption)
                                {
                                    throw unexpectedToken();
                                }
                            }
                            else
                            {
                                e = new ExpressionValue(
                                    (token.tokenType),
                                    SqlType.SQL_INTEGER);

                                read();
                            }

                            exprList.add(e);

                            i += elementCount;

                            continue;
                        }
                    case Tokens.OPENBRACKET:
                    case Tokens.CLOSEBRACKET:
                    case Tokens.COMMA:
                    default:
                        if (token.tokenType != exprType)
                        {
                            throw unexpectedToken();
                        }

                        read();

                        continue;
                }
            }
        }

        private Expression readSequenceExpression()
        {

            read();
            readThis(Tokens.VALUE);
            readThis(Tokens.FOR);
            checkIsSchemaObjectName();

            String schema = session.getSchemaName(token.namePrefix);
            NumberSequence sequence =
                database.schemaManager.getSequence(token.tokenString, schema,
                                                   true);

            read();

            Expression e = new ExpressionColumn(sequence);

            compileContext.addSequence(sequence);

            return e;
        }

        public QNameManager.QName readNewSchemaName()
        {

            checkIsSchemaObjectName();
            checkValidCatalogName(token.namePrefix);
            SqlInvariants.checkSchemaNameNotSystem(token.tokenString);

            QNameManager.QName name = database.nameManager.newHsqlName(token.tokenString,
                isDelimitedIdentifier(), SchemaObjectTypes.SCHEMA);

            read();

            return name;
        }

        public QNameManager.QName readNewSchemaObjectName(int type, bool checkSchema)
        {

            checkIsSchemaObjectName();

            QNameManager.QName hsqlName = database.nameManager.newHsqlName(token.tokenString,
                isDelimitedIdentifier(), type);

            if (token.namePrefix != null)
            {
                switch (type)
                {

                    case SchemaObjectTypes.LABEL:
                    case SchemaObjectTypes.VARIABLE:
                    case SchemaObjectTypes.SPECIFIC_ROUTINE:
                    case SchemaObjectTypes.GRANTEE:
                    case SchemaObjectTypes.CATALOG:
                        throw unexpectedToken();
                    case SchemaObjectTypes.CURSOR:
                        {
                            if (token.namePrePrefix == null
                                    && Tokens.T_MODULE.Equals(token.namePrefix)
                                    && !token.isDelimitedPrefix) { }
                            else
                            {
                                throw unexpectedTokenRequire(Tokens.T_MODULE);
                            }

                            break;
                        }
                    case SchemaObjectTypes.SCHEMA:
                        {
                            checkValidCatalogName(token.namePrefix);

                            if (token.namePrePrefix != null)
                            {
                                throw tooManyIdentifiers();
                            }

                            break;
                        }
                    case SchemaObjectTypes.COLUMN:
                        {
                            if (token.namePrefix != null)
                            {
                                throw tooManyIdentifiers();
                            }

                            break;
                        }
                    default:
                        {
                            checkValidCatalogName(token.namePrePrefix);

                            QNameManager.QName schemaName;

                            if (checkSchema)
                            {
                                schemaName =
                                    session.getSchemaQName(token.namePrefix);
                            }
                            else
                            {
                                schemaName =
                                    session.database.schemaManager.findSchemaHsqlName(
                                        token.namePrefix);

                                if (schemaName == null)
                                {
                                    schemaName = database.nameManager.newHsqlName(
                                        token.namePrefix, isDelimitedIdentifier(),
                                        SchemaObjectTypes.SCHEMA);
                                }
                            }

                            hsqlName.setSchemaIfNull(schemaName);

                            break;
                        }
                }
            }

            read();

            return hsqlName;
        }

        public QNameManager.QName readNewDependentSchemaObjectName(QNameManager.QName parentName, int type)
        {

            QNameManager.QName name = readNewSchemaObjectName(type, true);

            name.parent = parentName;

            name.setSchemaIfNull(parentName.schema);

            if (name.schema != null && parentName.schema != null
                    && name.schema != parentName.schema)
            {
                throw Error.error(ErrorCode.X_42505, token.namePrefix);
            }

            return name;
        }

        public QNameManager.QName readSchemaName()
        {

            checkIsSchemaObjectName();
            checkValidCatalogName(token.namePrefix);

            QNameManager.QName schema = session.getSchemaQName(token.tokenString);

            read();

            return schema;
        }

        public SchemaObject readSchemaObjectName(int type)
        {

            checkIsSchemaObjectName();
            checkValidCatalogName(token.namePrePrefix);

            SchemaObject obj =
                database.schemaManager.getSchemaObject(token.tokenString,
                    token.namePrefix, type);

            read();

            return obj;
        }

        public SchemaObject readSchemaObjectName(QNameManager.QName schemaName, int type)
        {

            checkIsSchemaObjectName();

            SchemaObject obj =
                database.schemaManager.getSchemaObject(token.tokenString,
                    schemaName.name, type);

            if (token.namePrefix != null)
            {
                if (!token.namePrefix.Equals(schemaName.name))
                {

                    // todo - better error message
                    throw Error.error(ErrorCode.X_42505, token.namePrefix);
                }

                if (token.namePrePrefix != null)
                {
                    if (!token.namePrePrefix.Equals(
                            database.getCatalogName().name))
                    {

                        // todo - better error message
                        throw Error.error(ErrorCode.X_42505, token.namePrefix);
                    }
                }
            }

            read();

            return obj;
        }

        public Table readTableName()
        {

            checkIsIdentifier();

            if (token.namePrePrefix != null)
            {
                checkValidCatalogName(token.namePrePrefix);
            }

            Table table = database.schemaManager.getTable(session,
                token.tokenString, token.namePrefix);

            read();

            return table;
        }

        ColumnSchema readSimpleColumnName(RangeVariable rangeVar)
        {

            ColumnSchema column = null;

            checkIsIdentifier();

            if (token.namePrefix != null)
            {
                throw tooManyIdentifiers();
            }

            int index = rangeVar.findColumn(token.tokenString);

            if (index > -1 && rangeVar.resolvesTableName(token.namePrefix)
                    && rangeVar.resolvesSchemaName(token.namePrePrefix))
            {
                column = rangeVar.getTable().getColumn(index);

                read();

                return column;
            }

            throw Error.error(ErrorCode.X_42501, token.tokenString);
        }

        ColumnSchema readSimpleColumnName(Table table)
        {

            checkIsIdentifier();

            if (token.namePrefix != null)
            {
                throw tooManyIdentifiers();
            }

            int index = table.findColumn(token.tokenString);

            if (index == -1)
            {
                throw Error.error(ErrorCode.X_42501, token.tokenString);
            }

            ColumnSchema column = table.getColumn(index);

            read();

            return column;
        }

        public ColumnSchema readColumnName(RangeVariable[] rangeVars)
        {

            ColumnSchema column = null;

            checkIsIdentifier();

            if (token.namePrePrePrefix != null)
            {
                checkValidCatalogName(token.namePrePrePrefix);
            }

            for (int i = 0; i < rangeVars.Length; i++)
            {
                int index = rangeVars[i].findColumn(token.tokenString);

                if (index > -1 && rangeVars[i].resolvesTableName(token.namePrefix)
                        && rangeVars[i].resolvesSchemaName(token.namePrePrefix))
                {
                    column = rangeVars[i].getColumn(index);

                    read();

                    return column;
                }
            }

            throw Error.error(ErrorCode.X_42501, token.tokenString);
        }

        public StatementDMQL compileDeclareCursor()
        {

            //int sensitivity = 0;    // ASENSITIVE
            //int scrollability = 0;    // NO_SCROLL
            //int holdability = 0;    // WITHOUT_HOLD
            //int returnability = 0;    // WITHOUT_RETURN

            readThis(Tokens.DECLARE);
            readNewSchemaObjectName(SchemaObjectTypes.CURSOR, true);

            switch (token.tokenType)
            {

                case Tokens.SENSITIVE:
                    read();

                    // sensitivity = SENSITIVE
                    break;

                case Tokens.INSENSITIVE:
                    read();

                    // sensitivity = INSENSITIVE
                    break;

                case Tokens.ASENSITIVE:
                    read();
                    break;
            }

            if (token.tokenType == Tokens.NO)
            {
                readThis(Tokens.SCROLL);
            }
            else
            {
                if (token.tokenType == Tokens.SCROLL)
                {
                    read();

                    // scrollability = SCROLL
                }
            }

            readThis(Tokens.CURSOR);

            for (int round = 0; round < 2; round++)
            {
                if (token.tokenType == Tokens.WITH)
                {
                    read();

                    if (round == 0 && token.tokenType == Tokens.HOLD)
                    {
                        read();

                        // holdability = HOLD
                    }
                    else
                    {
                        readThis(Tokens.RETURN);

                        round++;

                        // returnability = WITH_RETURN
                    }
                }
                else if (token.tokenType == Tokens.WITHOUT)
                {
                    read();

                    if (round == 0 && token.tokenType == Tokens.HOLD)
                    {
                        read();
                    }
                    else
                    {
                        readThis(Tokens.RETURN);

                        round++;
                    }
                }
            }

            readThis(Tokens.FOR);

            StatementDMQL cs = compileCursorSpecification();

            return cs;
        }

        /**
         * Retrieves a SELECT or other query expression Statement from this parse context.
         */
        public StatementDMQL compileCursorSpecification()
        {

            QueryExpression queryExpression = XreadQueryExpression();

            queryExpression.setAsTopLevel();
            queryExpression.resolve(session);

            if (token.tokenType == Tokens.FOR)
            {
                read();

                if (token.tokenType == Tokens.READ)
                {
                    read();
                    readThis(Tokens.ONLY);
                }
                else
                {
                    readThis(Tokens.UPDATE);

                    if (token.tokenType == Tokens.OF)
                    {
                        readThis(Tokens.OF);

                        OrderedHashSet colNames = readColumnNameList(null, false);
                    }
                }
            }

            StatementDMQL cs = new StatementQuery(session, queryExpression,
                                                  compileContext);

            return cs;
        }

       public int readCloseBrackets(int limit)
        {

            int count = 0;

            while (count < limit && token.tokenType == Tokens.CLOSEBRACKET)
            {
                read();

                count++;
            }

            return count;
        }

       public int readOpenBrackets()
        {

            int count = 0;

            while (token.tokenType == Tokens.OPENBRACKET)
            {
                count++;

                read();
            }

            return count;
        }

       public void checkValidCatalogName(String name)
        {

            if (name != null && !name.Equals(database.getCatalogName().name))
            {
                throw Error.error(ErrorCode.X_42501, name);
            }
        }

        public class CompileContext
        {

            //
            public int subQueryDepth;
            public EfzArrayList subQueryList = new EfzArrayList(true);
            public EfzArrayList parameters = new EfzArrayList(true);
            private EfzArrayList usedSequences = new EfzArrayList(true);
            private EfzArrayList usedRoutines = new EfzArrayList(true);
            private EfzArrayList rangeVariables = new EfzArrayList(true);
            public SqlType currentDomain;
            public bool contextuallyTypedExpression;
            public Session session;

            //
            private int rangeVarIndex = 0;

            public CompileContext(Session session)
            {

                this.session = session;

                reset();
            }

            public void reset()
            {

                rangeVarIndex = 0;

                rangeVariables.clear();
                subQueryList.clear();

                subQueryDepth = 0;

                parameters.clear();
                usedSequences.clear();
                usedRoutines.clear();

                //
                currentDomain = null;
                contextuallyTypedExpression = false;
            }

            public void registerRangeVariable(RangeVariable range)
            {

                range.rangePosition = getNextRangeVarIndex();
                range.level = subQueryDepth;

                rangeVariables.add(range);
            }

            public int getNextRangeVarIndex()
            {
                return rangeVarIndex++;
            }

            public int getRangeVarCount()
            {
                return rangeVarIndex;
            }

            public RangeVariable[] getRangeVariables()
            {

                RangeVariable[] array = new RangeVariable[rangeVariables.size()];

                rangeVariables.toArray(array);

                return array;
            }

            public NumberSequence[] getSequences()
            {

                if (usedSequences.size() == 0)
                {
                    return NumberSequence.emptyArray;
                }

                NumberSequence[] array = new NumberSequence[usedSequences.size()];

                usedSequences.toArray(array);

                return array;
            }

            public Routine[] getRoutines()
            {

                if (usedRoutines.size() == 0)
                {
                    return Routine.emptyArray;
                }

                OrderedHashSet set = new OrderedHashSet();

                for (int i = 0; i < usedRoutines.size(); i++)
                {
                    FunctionSQLInvoked function =
                        (FunctionSQLInvoked)usedRoutines.get(i);

                    set.add(function.routine);
                }

                Routine[] array = new Routine[set.size()];

                set.toArray(array);

                return array;
            }

            public  void addSequence(NumberSequence sequence)
            {
                usedSequences.add(sequence);
            }

            public void addRoutine(FunctionSQLInvoked function)
            {
                usedRoutines.add(function);
            }

            public void resetSubQueryLevel()
            {

                for (int i = rangeVariables.size() - 1; i >= 0; i--)
                {
                    RangeVariable range = (RangeVariable)rangeVariables.get(i);

                    if (range.level > subQueryDepth)
                    {
                        rangeVariables.remove(i);
                    }
                    else
                    {
                        rangeVarIndex = rangeVariables.size();

                        break;
                    }
                }

                for (int i = subQueryList.size() - 1; i >= 0; i--)
                {
                    SubQuery subQuery = (SubQuery)subQueryList.get(i);

                    if (subQuery.level > subQueryDepth)
                    {
                        subQueryList.remove(i);
                    }
                    else
                    {
                        break;
                    }
                }
            }

            /**
             * Return the list of subqueries as an array sorted according to the
             * order of materialization
             */
            public SubQuery[] getSubqueries()
            {

                if (subQueryList.size() == 0)
                {
                    return SubQuery.emptySubqueryArray;
                }

                subQueryList.sort((SubQuery)subQueryList.get(0));

                SubQuery[] subqueries = new SubQuery[subQueryList.size()];

                subQueryList.toArray(subqueries);
                subQueryList.clear();

                for (int i = 0; i < subqueries.Length; i++)
                {
                    subqueries[i].prepareTable(session);
                }

                return subqueries;
            }

            public ExpressionColumn[] getParameters()
            {

                if (parameters.size() == 0)
                {
                    return ExpressionColumn.emptyArray;
                }

                ExpressionColumn[] result =
                    (ExpressionColumn[])parameters.toArray(
                        new ExpressionColumn[parameters.size()]);

                parameters.clear();

                return result;
            }

            public void clearParameters()
            {
                parameters.clear();
            }

            public OrderedHashSet getSchemaObjectNames()
            {

                OrderedHashSet set = new OrderedHashSet();

                for (int i = 0; i < usedSequences.size(); i++)
                {
                    NumberSequence sequence =
                        (NumberSequence)usedSequences.get(i);

                    set.add(sequence.getName());
                }

                for (int i = 0; i < rangeVariables.size(); i++)
                {
                    RangeVariable range = (RangeVariable)rangeVariables.get(i);
                    QNameManager.QName name = range.rangeTable.getName();

                    if (name.schema != SqlInvariants.SYSTEM_SCHEMA_QNAME)
                    {
                        set.add(range.rangeTable.getName());
                        set.addAll(range.getColumnNames());
                    }
                    else if (name.type == SchemaObjectTypes.TRANSITION)
                    {
                        set.addAll(range.getColumnNames());
                    }
                }

                Routine[] routines = getRoutines();

                for (int i = 0; i < routines.Length; i++)
                {
                    set.add(routines[i].getSpecificName());
                }

                return set;
            }
        }
    }
}
